Skip to content
Merged
3 changes: 2 additions & 1 deletion cms/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"FSObject", "LargeObject",
# contest
"Contest", "Announcement", "ContestFolder", "TrainingProgram", "Student",
"TrainingDay", "TrainingDayGroup",
"TrainingDay", "TrainingDayGroup", "StudentTask",
# user
"User", "Team", "Participation", "Message", "Question", "DelayRequest",
# admin
Expand Down Expand Up @@ -109,6 +109,7 @@
from .training_day import TrainingDay
from .training_day_group import TrainingDayGroup
from .student import Student
from .student_task import StudentTask
from .user import User, Team, Participation, Message, Question, DelayRequest
from .task import Task, Statement, Attachment, Dataset, Manager, Testcase, \
Generator
Expand Down
9 changes: 8 additions & 1 deletion cms/db/student.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from . import Base

if typing.TYPE_CHECKING:
from . import TrainingProgram, Participation
from . import TrainingProgram, Participation, StudentTask


class Student(Base):
Expand Down Expand Up @@ -80,3 +80,10 @@ class Student(Base):
"Participation",
back_populates="student",
)

student_tasks: list["StudentTask"] = relationship(
"StudentTask",
back_populates="student",
cascade="all, delete-orphan",
passive_deletes=True,
)
96 changes: 96 additions & 0 deletions cms/db/student_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python3

# Contest Management System - http://cms-dev.github.io/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

"""StudentTask model for tracking which tasks each student has access to.

A StudentTask represents a task that has been assigned to a student,
either automatically when they start a training day, or manually by an admin.
This controls which tasks appear in the student's task archive and are
included in their score calculations.
"""

import typing
from datetime import datetime

from sqlalchemy.orm import relationship
from sqlalchemy.schema import Column, ForeignKey, UniqueConstraint
from sqlalchemy.types import DateTime, Integer

from . import Base

if typing.TYPE_CHECKING:
from . import Student, Task, TrainingDay


class StudentTask(Base):
"""A task assigned to a student.

Tracks which tasks each student has access to in the task archive.
Tasks can be assigned automatically when a student starts a training day
(source_training_day_id is set), or manually by an admin
(source_training_day_id is NULL).
"""
__tablename__ = "student_tasks"
__table_args__ = (
UniqueConstraint("student_id", "task_id",
name="student_tasks_student_id_task_id_key"),
)

id: int = Column(Integer, primary_key=True)

student_id: int = Column(
Integer,
ForeignKey("students.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False,
index=True,
)

task_id: int = Column(
Integer,
ForeignKey("tasks.id", onupdate="CASCADE", ondelete="CASCADE"),
nullable=False,
index=True,
)

# If set, the task was assigned when the student started this training day.
# If NULL, the task was manually assigned by an admin.
source_training_day_id: int | None = Column(
Integer,
ForeignKey("training_days.id", onupdate="CASCADE", ondelete="SET NULL"),
nullable=True,
index=True,
)

# When the task was assigned to the student.
assigned_at: datetime = Column(
DateTime,
nullable=False,
)

student: "Student" = relationship(
"Student",
back_populates="student_tasks",
)

task: "Task" = relationship(
"Task",
back_populates="student_tasks",
)

source_training_day: "TrainingDay | None" = relationship(
"TrainingDay",
)
7 changes: 7 additions & 0 deletions cms/db/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from . import Submission, UserTest, TrainingDay
from .scorecache import ParticipationTaskScore
from .modelsolution import ModelSolutionMeta
from .student_task import StudentTask


class Task(Base):
Expand Down Expand Up @@ -324,6 +325,12 @@ class Task(Base):
passive_deletes=True,
back_populates="task")

student_tasks: list["StudentTask"] = relationship(
"StudentTask",
cascade="all, delete-orphan",
passive_deletes=True,
back_populates="task")

def get_allowed_languages(self) -> list[str]:
"""Get the list of allowed languages for this task.

Expand Down
8 changes: 8 additions & 0 deletions cms/server/admin/handlers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@
TrainingProgramQuestionsHandler, \
StudentHandler, \
StudentTagsHandler, \
StudentTasksHandler, \
AddStudentTaskHandler, \
RemoveStudentTaskHandler, \
BulkAssignTaskHandler, \
TrainingProgramTrainingDaysHandler, \
AddTrainingDayHandler, \
RemoveTrainingDayHandler, \
Expand Down Expand Up @@ -334,6 +338,10 @@
(r"/training_program/([0-9]+)/student/([0-9]+)/remove", RemoveTrainingProgramStudentHandler),
(r"/training_program/([0-9]+)/student/([0-9]+)/edit", StudentHandler),
(r"/training_program/([0-9]+)/student/([0-9]+)/tags", StudentTagsHandler),
(r"/training_program/([0-9]+)/student/([0-9]+)/tasks", StudentTasksHandler),
(r"/training_program/([0-9]+)/student/([0-9]+)/tasks/add", AddStudentTaskHandler),
(r"/training_program/([0-9]+)/student/([0-9]+)/task/([0-9]+)/remove", RemoveStudentTaskHandler),
(r"/training_program/([0-9]+)/bulk_assign_task", BulkAssignTaskHandler),
(r"/training_program/([0-9]+)/tasks", TrainingProgramTasksHandler),
(r"/training_program/([0-9]+)/tasks/add", AddTrainingProgramTaskHandler),
(r"/training_program/([0-9]+)/task/([0-9]+)/remove", RemoveTrainingProgramTaskHandler),
Expand Down
Loading
Loading