From 6ea6c749af4c2fc51de6fea0884e5128a8931a7a Mon Sep 17 00:00:00 2001 From: codEnjoyer Date: Wed, 15 Nov 2023 12:14:39 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B5=D1=81=D0=BA=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D0=BE=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=BE=D0=B2=20+?= =?UTF-8?q?=20=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=BD=D1=8B=D0=B5?= =?UTF-8?q?=20=D1=81=D1=85=D0=B5=D0=BC=D1=8B=20=D0=B2=D0=BE=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/migrations/env.py | 8 +-- ...20\262\320\265\321\202\320\276\320\262.py" | 60 +++++++++++++++++++ backend/src/game/levels/models.py | 8 +-- backend/src/game/units/tasks/models.py | 39 +++++++++--- .../src/game/units/tasks/questions/models.py | 40 +++++++++++++ .../src/game/units/tasks/questions/schemas.py | 30 ++++++++++ backend/src/game/units/theory/models.py | 4 +- backend/src/users/employees/models.py | 2 + 8 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 "backend/migrations/versions/2023_11_15_1205-5bc05f78249a_\320\264\320\276\320\261\320\260\320\262\320\273\321\217\320\265\321\202_\321\202\320\260\320\261\320\273\320\270\321\206\321\213_\320\262\320\276\320\277\321\200\320\276\321\201\320\276\320\262_\320\270_\320\276\321\202\320\262\320\265\321\202\320\276\320\262.py" create mode 100644 backend/src/game/units/tasks/questions/models.py create mode 100644 backend/src/game/units/tasks/questions/schemas.py diff --git a/backend/migrations/env.py b/backend/migrations/env.py index a5ca94c..fa6a85a 100644 --- a/backend/migrations/env.py +++ b/backend/migrations/env.py @@ -8,7 +8,6 @@ from sqlalchemy.ext.asyncio import async_engine_from_config from alembic import context - sys.path.append(os.path.join(sys.path[0], 'src')) from settings import Settings @@ -32,8 +31,9 @@ from game.map.models import Map from game.modules.models import Module from game.levels.models import Level -from game.units.tasks.models import Task -from game.units.theory.models import Theory, TheoryVideo +from game.units.tasks.models import TaskUnit, EmployeesTasks +from game.units.tasks.questions.models import Question, Answer +from game.units.theory.models import TheoryUnit, TheoryVideo from users.models import User from users.tutors.models import Tutor @@ -41,8 +41,6 @@ from database import BaseModel - -# TODO: Добавить metadata из моделей target_metadata = BaseModel.metadata diff --git "a/backend/migrations/versions/2023_11_15_1205-5bc05f78249a_\320\264\320\276\320\261\320\260\320\262\320\273\321\217\320\265\321\202_\321\202\320\260\320\261\320\273\320\270\321\206\321\213_\320\262\320\276\320\277\321\200\320\276\321\201\320\276\320\262_\320\270_\320\276\321\202\320\262\320\265\321\202\320\276\320\262.py" "b/backend/migrations/versions/2023_11_15_1205-5bc05f78249a_\320\264\320\276\320\261\320\260\320\262\320\273\321\217\320\265\321\202_\321\202\320\260\320\261\320\273\320\270\321\206\321\213_\320\262\320\276\320\277\321\200\320\276\321\201\320\276\320\262_\320\270_\320\276\321\202\320\262\320\265\321\202\320\276\320\262.py" new file mode 100644 index 0000000..de15bce --- /dev/null +++ "b/backend/migrations/versions/2023_11_15_1205-5bc05f78249a_\320\264\320\276\320\261\320\260\320\262\320\273\321\217\320\265\321\202_\321\202\320\260\320\261\320\273\320\270\321\206\321\213_\320\262\320\276\320\277\321\200\320\276\321\201\320\276\320\262_\320\270_\320\276\321\202\320\262\320\265\321\202\320\276\320\262.py" @@ -0,0 +1,60 @@ +"""Добавляет таблицы вопросов и ответов + +Revision ID: 5bc05f78249a +Revises: 5aa9681ef1db +Create Date: 2023-11-15 12:05:18.429020 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '5bc05f78249a' +down_revision: Union[str, None] = '5aa9681ef1db' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('answers', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('question_id', sa.UUID(), nullable=False), + sa.Column('content', sa.String(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('questions', + sa.Column('id', sa.UUID(), nullable=False), + sa.Column('type', sa.Enum(name='question_types'), nullable=False), + sa.Column('question', sa.String(), nullable=False), + sa.Column('correct_answer_id', sa.UUID(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('task_employees', + sa.Column('task_id', sa.UUID(), nullable=False), + sa.Column('employee_id', sa.UUID(), nullable=False), + sa.Column('state', sa.Enum(name='task_states'), nullable=False), + sa.ForeignKeyConstraint(['employee_id'], ['employees.id'], ), + sa.ForeignKeyConstraint(['task_id'], ['tasks.id'], ), + sa.PrimaryKeyConstraint('task_id', 'employee_id') + ) + op.add_column('tasks', sa.Column('type', sa.Enum(name='task_types'), nullable=False)) + op.add_column('tasks', sa.Column('requires_review', sa.Boolean(), nullable=False)) + op.drop_column('tasks', 'content') + op.drop_column('tasks', 'is_accomplished') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('tasks', sa.Column('is_accomplished', sa.BOOLEAN(), autoincrement=False, nullable=False)) + op.add_column('tasks', sa.Column('content', sa.TEXT(), autoincrement=False, nullable=False)) + op.drop_column('tasks', 'requires_review') + op.drop_column('tasks', 'type') + op.drop_table('task_employees') + op.drop_table('questions') + op.drop_table('answers') + # ### end Alembic commands ### diff --git a/backend/src/game/levels/models.py b/backend/src/game/levels/models.py index 6487f3f..8f3f13b 100644 --- a/backend/src/game/levels/models.py +++ b/backend/src/game/levels/models.py @@ -8,8 +8,8 @@ if typing.TYPE_CHECKING: from game.modules.models import Module - from game.units.tasks.models import Task - from game.units.theory.models import Theory + from game.units.tasks.models import TaskUnit + from game.units.theory.models import TheoryUnit class Level(BaseModel): @@ -21,5 +21,5 @@ class Level(BaseModel): is_accomplished: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) module: Mapped["Module"] = relationship(back_populates='level') - theory: Mapped[list["Theory"]] = relationship(back_populates='level') - tasks: Mapped[list["Task"]] = relationship(back_populates='level') + theory: Mapped[list["TheoryUnit"]] = relationship(back_populates='level') + tasks: Mapped[list["TaskUnit"]] = relationship(back_populates='level') diff --git a/backend/src/game/units/tasks/models.py b/backend/src/game/units/tasks/models.py index 017f358..a4002a7 100644 --- a/backend/src/game/units/tasks/models.py +++ b/backend/src/game/units/tasks/models.py @@ -1,21 +1,44 @@ import uuid +import enum +from typing import TYPE_CHECKING -from sqlalchemy import UUID, Boolean, Integer, Text -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import UUID, Integer, Text, Enum, ForeignKey, Boolean +from sqlalchemy.orm import Mapped, mapped_column, relationship from database import BaseModel +if TYPE_CHECKING: + from questions.models import Question -class Task(BaseModel): + +class TaskTypes(enum.Enum): + Test = enum.auto() + + +class TaskStates(enum.Enum): + NotViewed = enum.auto() + Viewed = enum.auto() + Submitted = enum.auto() + Finished = enum.auto() + + +class TaskUnit(BaseModel): __tablename__ = 'tasks' id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) - is_accomplished: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) - # TODO: Задание не может быть выполнено глобально, нужна таблица для выполненных заданий пользователями - # requires_review: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + type: Mapped[TaskTypes] = mapped_column(Enum(name='task_types'), nullable=False, default=TaskTypes.Test) + requires_review: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) score_reward: Mapped[int] = mapped_column(Integer, nullable=False, default=1) - content: Mapped[str] = mapped_column(Text, nullable=False, default="Текст задания") - # proof: Mapped[URL] = mapped_column(URLType, nullable=True, default=None) + + questions: Mapped[list["Question"]] = relationship() + + +class EmployeesTasks(BaseModel): + __tablename__ = 'task_employees' + + task_id: Mapped[uuid.UUID] = mapped_column(UUID, ForeignKey('tasks.id'), primary_key=True) + employee_id: Mapped[uuid.UUID] = mapped_column(UUID, ForeignKey('employees.id'), primary_key=True) + state: Mapped[TaskStates] = mapped_column(Enum(name='task_states'), nullable=False, default=TaskStates.NotViewed) # class Proof(BaseModel): # __tablename__ = "proofs" diff --git a/backend/src/game/units/tasks/questions/models.py b/backend/src/game/units/tasks/questions/models.py new file mode 100644 index 0000000..e37d158 --- /dev/null +++ b/backend/src/game/units/tasks/questions/models.py @@ -0,0 +1,40 @@ +import uuid +import enum +from typing import TYPE_CHECKING + +from sqlalchemy import UUID, Enum, String +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from database import BaseModel + +if TYPE_CHECKING: + from game.units.tasks.models import TaskUnit + + +class QuestionTypes(enum.Enum): + SingleChoice = enum.auto() + MultipleChoice = enum.auto() + + +class Question(BaseModel): + __tablename__ = 'questions' + + id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) + type: Mapped[QuestionTypes] = mapped_column( + Enum(name='question_types'), nullable=False, default=QuestionTypes.SingleChoice) + question: Mapped[str] = mapped_column(String, nullable=False, default="Вопрос!") + correct_answer_id: Mapped[uuid.UUID] = mapped_column(UUID, default=uuid.uuid4) + + task: Mapped["TaskUnit"] = relationship(back_populates='questions') + possible_answers: Mapped[list["Answer"]] = relationship(back_populates='question') + correct_answers: Mapped[list["Answer"]] = relationship(back_populates='question') + + +class Answer(BaseModel): + __tablename__ = 'answers' + + id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) + question_id: Mapped[uuid.UUID] = mapped_column(UUID, default=uuid.uuid4) + content: Mapped[str] = mapped_column(String, nullable=False, default='Ответ?') + + question: Mapped["Question"] = relationship(back_populates='answers') diff --git a/backend/src/game/units/tasks/questions/schemas.py b/backend/src/game/units/tasks/questions/schemas.py new file mode 100644 index 0000000..4486830 --- /dev/null +++ b/backend/src/game/units/tasks/questions/schemas.py @@ -0,0 +1,30 @@ +import uuid +from uuid import UUID + +from pydantic import BaseModel, ConfigDict + +from game.units.tasks.questions.models import QuestionTypes + + +class __QuestionBase(BaseModel): + type: QuestionTypes + question: str + possible_answers: list[str] + + model_config = ConfigDict(from_attributes=True) + + +class TaskRead(__QuestionBase): + id: UUID + task_id: UUID + + +class TaskCreate(__QuestionBase): + correct_answers: list[str] + task_id: UUID + + +class TaskUpdate(__QuestionBase): + question: str | None + possible_answers: list[str] | None + correct_answers: list[str] | None diff --git a/backend/src/game/units/theory/models.py b/backend/src/game/units/theory/models.py index 839457f..b5f2c25 100644 --- a/backend/src/game/units/theory/models.py +++ b/backend/src/game/units/theory/models.py @@ -7,7 +7,7 @@ from database import BaseModel -class Theory(BaseModel): +class TheoryUnit(BaseModel): __tablename__ = 'theory_blocks' id: Mapped[uuid.UUID] = mapped_column(UUID, primary_key=True, default=uuid.uuid4) @@ -24,4 +24,4 @@ class TheoryVideo(BaseModel): theory_id: Mapped[uuid.UUID] = mapped_column(UUID, ForeignKey('theory_blocks.id'), nullable=False) url: Mapped[URL] = mapped_column(URLType, nullable=False) - theory: Mapped["Theory"] = relationship(back_populates="theory_video") + theory: Mapped["TheoryUnit"] = relationship(back_populates="theory_video") diff --git a/backend/src/users/employees/models.py b/backend/src/users/employees/models.py index 5f9c8f4..35b6108 100644 --- a/backend/src/users/employees/models.py +++ b/backend/src/users/employees/models.py @@ -10,6 +10,7 @@ if typing.TYPE_CHECKING: from users.tutors.models import Tutor + from game.units.tasks.models import TaskUnit class Employee(BaseModel): @@ -23,3 +24,4 @@ class Employee(BaseModel): hired_at: Mapped[datetime.date] = mapped_column(Date, server_default=func.current_date()) tutor: Mapped["Tutor"] = relationship(back_populates='employee') + tasks: Mapped[list["TaskUnit"]] = relationship(secondary="task_employees", back_populates='employees')