Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tests/managers/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ def test_export(db: AppDatabase):
c = AppConfigManager(lambda: dict())
ach = AchievementManager(c)
ext = ExternalTaskManager(db.groups, db.tasks)
s = StatusManager(db.tasks, db.groups, db.variants, db.statuses, c, db.seeds, db.checks, ach, ext)
s = StatusManager(db.tasks, db.groups, db.variants, db.statuses, c, db.seeds, db.checks, ach, ext, db.students)
m = StudentManager(c, db.students, db.mailers)
e = ExportManager(db.groups, db.messages, s, db.variants, db.tasks, db.students, m)
e = ExportManager(db.groups, db.messages, s, db.statuses, db.variants, db.tasks, db.students, m)
(group, variant, task) = arrange_task(db)

code = "main = lambda x: x**42"
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/test_task_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ def test_task_status_html_status(db: AppDatabase, client: FlaskClient):
c = 'd-block text-center text-decoration-none p-1 position-relative'
for ok, text, color in [
(False, 'x', 'background-color:#ffe3ee'),
(True, '+', 'background-color:#e3ffee'),
(False, '+', 'background-color:#e3ffee'),
(True, '+', 'background-color:#e3ffee'),
(True, '+', 'background-color:#fff9e3'),
(False, '+', 'background-color:#fff9e3'),
(True, '+', 'background-color:#fff9e3'),
]:
db.statuses.check(tasks_id, variant_id, group_id, unique_str(), ok, unique_str(), unique_str())
response = client.get(f'/group/{group_id}')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""add_reviewer_to_task_status

Revision ID: 2a3942c432db
Revises: 3782564289c1
Create Date: 2026-02-01 13:22:32.287727

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '2a3942c432db'
down_revision = '3782564289c1'
branch_labels = None
depends_on = None


def upgrade():
with op.batch_alter_table('task_statuses') as bop:
bop.add_column(sa.Column('reviewer', sa.Integer, nullable=True))
bop.create_foreign_key('fk_task_statuses_reviewer', 'students', ['reviewer'], ['id'])


def downgrade():
with op.batch_alter_table("task_statuses") as bop:
bop.drop_constraint("fk_task_statuses_reviewer", type_="foreignkey")
bop.drop_column("reviewer")
35 changes: 35 additions & 0 deletions webapp/alembic/versions/20260201.14-02.add_task_blocks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""add_task_blocks

Revision ID: a8e656f1b1df
Revises: 2a3942c432db
Create Date: 2026-02-01 14:02:29.220477

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'a8e656f1b1df'
down_revision = '2a3942c432db'
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"task_blocks",
sa.Column("id", sa.Integer, primary_key=True, nullable=False),
sa.Column("title", sa.String, nullable=False),
sa.Column("weight", sa.Integer, nullable=False),
)
with op.batch_alter_table('tasks') as bop:
bop.add_column(sa.Column('block', sa.Integer, nullable=True))
bop.create_foreign_key('fk_tasks_block', 'task_blocks', ['block'], ['id'])


def downgrade():
with op.batch_alter_table("tasks") as bop:
bop.drop_constraint("fk_tasks_block", type_="foreignkey")
bop.drop_column("block")
op.drop_table("task_blocks")
73 changes: 52 additions & 21 deletions webapp/dto.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from flask import Config

from webapp.models import FinalSeed, Group, Status, Student, Task, TaskStatus, TypeOfTask, Variant
from webapp.models import FinalSeed, Group, Status, Student, Task, TaskBlock, TaskStatus, TypeOfTask, Variant


class AppConfig:
Expand Down Expand Up @@ -43,11 +43,13 @@ def __init__(self, group: int, group_title: str, task: int, variant: int, active


class TaskDto:
def __init__(self, task: Task, seed: FinalSeed | None):
def __init__(self, task: Task, block: TaskBlock | None, seed: FinalSeed | None):
self.id = int(task.id)
self.formulation = task.formulation
self.active = task.type == TypeOfTask.Static or seed and seed.active
self.is_random = task.type == TypeOfTask.Random
self.block_title = block.title if block else ''
self.block = block and block.id


class AchievementDto:
Expand Down Expand Up @@ -113,9 +115,12 @@ def formulation_url(self) -> str:
def cell_background(self) -> str:
return self.map_status({
Status.Submitted: "inherit",
Status.Checked: "#e3ffee",
Status.CheckedSubmitted: "#e3ffee",
Status.CheckedFailed: "#e3ffee",
Status.Checked: "#fff9e3",
Status.CheckedSubmitted: "#fff9e3",
Status.CheckedFailed: "#fff9e3",
Status.Verified: "#e3ffee",
Status.VerifiedSubmitted: "#e3ffee",
Status.VerifiedFailed: "#e3ffee",
Status.Failed: "#ffe3ee",
Status.NotSubmitted: "inherit",
})
Expand All @@ -127,6 +132,9 @@ def name(self) -> str:
Status.Checked: "Зачтено",
Status.CheckedSubmitted: "Зачтено. Отправлено повторно",
Status.CheckedFailed: "Зачтено. Ошибка при повторной отправке!",
Status.Verified: "Защищено",
Status.VerifiedSubmitted: "Защищено. Отправлено повторно",
Status.VerifiedFailed: "Защищено. Ошибка при повторной отправке!",
Status.Failed: "Ошибка!",
Status.NotSubmitted: "Не отправлено",
})
Expand All @@ -138,37 +146,59 @@ def code(self) -> str:
Status.Checked: "+",
Status.CheckedSubmitted: "+",
Status.CheckedFailed: "+",
Status.Verified: "✓",
Status.VerifiedSubmitted: "✓",
Status.VerifiedFailed: "✓",
Status.Failed: "x",
Status.NotSubmitted: "-",
})

@property
def color(self) -> str:
return self.map_status({
Status.Submitted: "primary",
Status.Checked: "success",
Status.CheckedSubmitted: "success",
Status.CheckedFailed: "success",
Status.Submitted: "secondary",
Status.Checked: "warning",
Status.CheckedSubmitted: "warning",
Status.CheckedFailed: "warning",
Status.Verified: "success",
Status.VerifiedSubmitted: "success",
Status.VerifiedFailed: "success",
Status.Failed: "danger",
Status.NotSubmitted: "secondary",
})

@property
def show_achievements(self) -> bool:
return self.achievements and self.status in [
Status.Checked,
Status.CheckedSubmitted,
Status.CheckedFailed,
Status.Verified,
Status.VerifiedSubmitted,
Status.VerifiedFailed,
]

@property
def can_verify(self) -> bool:
return self.status in [
Status.Checked,
Status.CheckedSubmitted,
Status.CheckedFailed,
]

@property
def can_unverify(self) -> bool:
return self.status in [
Status.Verified,
Status.VerifiedSubmitted,
Status.VerifiedFailed,
]

@property
def disabled(self) -> bool:
active = self.external.active
return not active or self.readonly

@property
def show_achievements(self) -> bool:
return self.achievements and self.map_status({
Status.Submitted: False,
Status.Failed: False,
Status.NotSubmitted: False,
Status.Checked: True,
Status.CheckedSubmitted: True,
Status.CheckedFailed: True,
})

def map_achievements(self, status: TaskStatus | None, achievements: list[int]):
dtos = []
for order, count in enumerate(achievements):
Expand All @@ -182,8 +212,9 @@ def map_status(self, map: dict[Status, str]):


class VariantDto:
def __init__(self, variant: Variant, statuses: list[TaskStatusDto]):
def __init__(self, variant: Variant, statuses: list[TaskStatusDto], student: Student | None):
self.id = int(variant.id)
self.email = student.email if student else ''
self.statuses = statuses
self.earned = sum(s.earned for s in statuses if s.earned > 1)
self.solved = sum(s.status in [
Expand Down
Loading