mirror of
https://github.com/koloideal/Quizzi.git
synced 2026-06-10 10:25:28 +03:00
Initial commit
This commit is contained in:
@@ -12,3 +12,33 @@ class User:
|
|||||||
is_admin: bool = False
|
is_admin: bool = False
|
||||||
created_at: datetime | None = None
|
created_at: datetime | None = None
|
||||||
updated_at: datetime | None = None
|
updated_at: datetime | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Test:
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
description: str | None = None
|
||||||
|
for_group: int | None = None
|
||||||
|
is_active: bool = True
|
||||||
|
created_at: datetime | None = None
|
||||||
|
updated_at: datetime | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Question:
|
||||||
|
id: int
|
||||||
|
test_id: int
|
||||||
|
text: str
|
||||||
|
position: int = 0
|
||||||
|
question_type: str = "single"
|
||||||
|
tg_file_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Option:
|
||||||
|
id: int
|
||||||
|
question_id: int
|
||||||
|
text: str
|
||||||
|
is_correct: bool = False
|
||||||
|
explanation: str | None = None
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from trudex.domain.schemas import Option as DomainOption
|
||||||
|
from trudex.infrastructure.database.dto.option import OptionDTO
|
||||||
|
from trudex.infrastructure.database.models import Option
|
||||||
|
|
||||||
|
|
||||||
|
class OptionDAO:
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self.session: AsyncSession = session
|
||||||
|
|
||||||
|
async def get_by_id(self, option_id: int) -> DomainOption | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Option).where(Option.id == option_id)
|
||||||
|
)
|
||||||
|
model = result.scalar_one_or_none()
|
||||||
|
return OptionDTO(model).to_domain() if model else None
|
||||||
|
|
||||||
|
async def get_all(self) -> list[DomainOption]:
|
||||||
|
result = await self.session.execute(select(Option))
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [OptionDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def create(
|
||||||
|
self,
|
||||||
|
question_id: int,
|
||||||
|
text: str,
|
||||||
|
is_correct: bool = False,
|
||||||
|
explanation: str | None = None,
|
||||||
|
) -> DomainOption:
|
||||||
|
option = Option(
|
||||||
|
question_id=question_id,
|
||||||
|
text=text,
|
||||||
|
is_correct=is_correct,
|
||||||
|
explanation=explanation,
|
||||||
|
)
|
||||||
|
self.session.add(option)
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(option)
|
||||||
|
return OptionDTO(option).to_domain()
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
option_id: int,
|
||||||
|
text: str | None = None,
|
||||||
|
is_correct: bool | None = None,
|
||||||
|
explanation: str | None = None,
|
||||||
|
) -> DomainOption | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Option).where(Option.id == option_id)
|
||||||
|
)
|
||||||
|
option = result.scalar_one_or_none()
|
||||||
|
if not option:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if text is not None:
|
||||||
|
option.text = text
|
||||||
|
if is_correct is not None:
|
||||||
|
option.is_correct = is_correct
|
||||||
|
if explanation is not None:
|
||||||
|
option.explanation = explanation
|
||||||
|
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(option)
|
||||||
|
return OptionDTO(option).to_domain()
|
||||||
|
|
||||||
|
async def delete(self, option_id: int) -> bool:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Option).where(Option.id == option_id)
|
||||||
|
)
|
||||||
|
option = result.scalar_one_or_none()
|
||||||
|
if not option:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await self.session.delete(option)
|
||||||
|
await self.session.flush()
|
||||||
|
return True
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from trudex.domain.schemas import Question as DomainQuestion
|
||||||
|
from trudex.infrastructure.database.dto.question import QuestionDTO
|
||||||
|
from trudex.infrastructure.database.models import Question, QuestionType
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionDAO:
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self.session: AsyncSession = session
|
||||||
|
|
||||||
|
async def get_by_id(self, question_id: int) -> DomainQuestion | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Question).where(Question.id == question_id)
|
||||||
|
)
|
||||||
|
model = result.scalar_one_or_none()
|
||||||
|
return QuestionDTO(model).to_domain() if model else None
|
||||||
|
|
||||||
|
async def get_all(self) -> list[DomainQuestion]:
|
||||||
|
result = await self.session.execute(select(Question))
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [QuestionDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def create(
|
||||||
|
self,
|
||||||
|
test_id: int,
|
||||||
|
text: str,
|
||||||
|
position: int = 0,
|
||||||
|
question_type: str = "single",
|
||||||
|
tg_file_id: str | None = None,
|
||||||
|
) -> DomainQuestion:
|
||||||
|
question = Question(
|
||||||
|
test_id=test_id,
|
||||||
|
text=text,
|
||||||
|
position=position,
|
||||||
|
question_type=QuestionType(question_type),
|
||||||
|
tg_file_id=tg_file_id,
|
||||||
|
)
|
||||||
|
self.session.add(question)
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(question)
|
||||||
|
return QuestionDTO(question).to_domain()
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
question_id: int,
|
||||||
|
text: str | None = None,
|
||||||
|
position: int | None = None,
|
||||||
|
question_type: str | None = None,
|
||||||
|
tg_file_id: str | None = None,
|
||||||
|
) -> DomainQuestion | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Question).where(Question.id == question_id)
|
||||||
|
)
|
||||||
|
question = result.scalar_one_or_none()
|
||||||
|
if not question:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if text is not None:
|
||||||
|
question.text = text
|
||||||
|
if position is not None:
|
||||||
|
question.position = position
|
||||||
|
if question_type is not None:
|
||||||
|
question.question_type = QuestionType(question_type)
|
||||||
|
if tg_file_id is not None:
|
||||||
|
question.tg_file_id = tg_file_id
|
||||||
|
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(question)
|
||||||
|
return QuestionDTO(question).to_domain()
|
||||||
|
|
||||||
|
async def delete(self, question_id: int) -> bool:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Question).where(Question.id == question_id)
|
||||||
|
)
|
||||||
|
question = result.scalar_one_or_none()
|
||||||
|
if not question:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await self.session.delete(question)
|
||||||
|
await self.session.flush()
|
||||||
|
return True
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from trudex.domain.schemas import Test as DomainTest
|
||||||
|
from trudex.infrastructure.database.dto.test import TestDTO
|
||||||
|
from trudex.infrastructure.database.models import Test
|
||||||
|
|
||||||
|
|
||||||
|
class TestDAO:
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self.session: AsyncSession = session
|
||||||
|
|
||||||
|
async def get_by_id(self, test_id: int) -> DomainTest | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Test).where(Test.id == test_id)
|
||||||
|
)
|
||||||
|
model = result.scalar_one_or_none()
|
||||||
|
return TestDTO(model).to_domain() if model else None
|
||||||
|
|
||||||
|
async def get_all(self) -> list[DomainTest]:
|
||||||
|
result = await self.session.execute(select(Test))
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [TestDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def create(
|
||||||
|
self,
|
||||||
|
title: str,
|
||||||
|
description: str | None = None,
|
||||||
|
for_group: int | None = None,
|
||||||
|
is_active: bool = True,
|
||||||
|
) -> DomainTest:
|
||||||
|
test = Test(
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
for_group=for_group,
|
||||||
|
is_active=is_active,
|
||||||
|
)
|
||||||
|
self.session.add(test)
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(test)
|
||||||
|
return TestDTO(test).to_domain()
|
||||||
|
|
||||||
|
async def update(
|
||||||
|
self,
|
||||||
|
test_id: int,
|
||||||
|
title: str | None = None,
|
||||||
|
description: str | None = None,
|
||||||
|
for_group: int | None = None,
|
||||||
|
is_active: bool | None = None,
|
||||||
|
) -> DomainTest | None:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Test).where(Test.id == test_id)
|
||||||
|
)
|
||||||
|
test = result.scalar_one_or_none()
|
||||||
|
if not test:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if title is not None:
|
||||||
|
test.title = title
|
||||||
|
if description is not None:
|
||||||
|
test.description = description
|
||||||
|
if for_group is not None:
|
||||||
|
test.for_group = for_group
|
||||||
|
if is_active is not None:
|
||||||
|
test.is_active = is_active
|
||||||
|
|
||||||
|
await self.session.flush()
|
||||||
|
await self.session.refresh(test)
|
||||||
|
return TestDTO(test).to_domain()
|
||||||
|
|
||||||
|
async def delete(self, test_id: int) -> bool:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(Test).where(Test.id == test_id)
|
||||||
|
)
|
||||||
|
test = result.scalar_one_or_none()
|
||||||
|
if not test:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await self.session.delete(test)
|
||||||
|
await self.session.flush()
|
||||||
|
return True
|
||||||
@@ -22,20 +22,6 @@ class UserDAO:
|
|||||||
models = list(result.scalars().all())
|
models = list(result.scalars().all())
|
||||||
return [UserDTO(model).to_domain() for model in models]
|
return [UserDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
async def get_by_group(self, group: int) -> list[DomainUser]:
|
|
||||||
result = await self.session.execute(
|
|
||||||
select(User).where(User.group == group)
|
|
||||||
)
|
|
||||||
models = list(result.scalars().all())
|
|
||||||
return [UserDTO(model).to_domain() for model in models]
|
|
||||||
|
|
||||||
async def get_admins(self) -> list[DomainUser]:
|
|
||||||
result = await self.session.execute(
|
|
||||||
select(User).where(User.is_admin == True)
|
|
||||||
)
|
|
||||||
models = list(result.scalars().all())
|
|
||||||
return [UserDTO(model).to_domain() for model in models]
|
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
from trudex.domain.schemas import Option as DomainOption
|
||||||
|
from trudex.infrastructure.database.models import Option as OptionModel
|
||||||
|
|
||||||
|
|
||||||
|
class OptionDTO:
|
||||||
|
def __init__(self, model: OptionModel) -> None:
|
||||||
|
self.model: OptionModel = model
|
||||||
|
|
||||||
|
def to_domain(self) -> DomainOption:
|
||||||
|
return DomainOption(
|
||||||
|
id=self.model.id,
|
||||||
|
question_id=self.model.question_id,
|
||||||
|
text=self.model.text,
|
||||||
|
is_correct=self.model.is_correct,
|
||||||
|
explanation=self.model.explanation,
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from trudex.domain.schemas import Question as DomainQuestion
|
||||||
|
from trudex.infrastructure.database.models import Question as QuestionModel
|
||||||
|
|
||||||
|
|
||||||
|
class QuestionDTO:
|
||||||
|
def __init__(self, model: QuestionModel) -> None:
|
||||||
|
self.model: QuestionModel = model
|
||||||
|
|
||||||
|
def to_domain(self) -> DomainQuestion:
|
||||||
|
return DomainQuestion(
|
||||||
|
id=self.model.id,
|
||||||
|
test_id=self.model.test_id,
|
||||||
|
text=self.model.text,
|
||||||
|
position=self.model.position,
|
||||||
|
question_type=self.model.question_type.value,
|
||||||
|
tg_file_id=self.model.tg_file_id,
|
||||||
|
)
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from trudex.domain.schemas import Test as DomainTest
|
||||||
|
from trudex.infrastructure.database.models import Test as TestModel
|
||||||
|
|
||||||
|
|
||||||
|
class TestDTO:
|
||||||
|
def __init__(self, model: TestModel) -> None:
|
||||||
|
self.model: TestModel = model
|
||||||
|
|
||||||
|
def to_domain(self) -> DomainTest:
|
||||||
|
return DomainTest(
|
||||||
|
id=self.model.id,
|
||||||
|
title=self.model.title,
|
||||||
|
description=self.model.description,
|
||||||
|
for_group=self.model.for_group,
|
||||||
|
is_active=self.model.is_active,
|
||||||
|
created_at=self.model.created_at,
|
||||||
|
updated_at=self.model.updated_at,
|
||||||
|
)
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from tokenize import group
|
|
||||||
from typing import final
|
from typing import final
|
||||||
|
|
||||||
from sqlalchemy import BigInteger, CheckConstraint, ForeignKey, Integer, String, Text, func
|
from sqlalchemy import BigInteger, CheckConstraint, ForeignKey, Integer, String, Text, func
|
||||||
@@ -30,6 +29,7 @@ class QuestionType(str, Enum):
|
|||||||
MULTIPLE = "multiple"
|
MULTIPLE = "multiple"
|
||||||
INPUT = "input"
|
INPUT = "input"
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Test(Base):
|
class Test(Base):
|
||||||
__tablename__ = "tests"
|
__tablename__ = "tests"
|
||||||
@@ -37,8 +37,10 @@ class Test(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
title: Mapped[str] = mapped_column(String(255))
|
title: Mapped[str] = mapped_column(String(255))
|
||||||
description: Mapped[str | None] = mapped_column(Text)
|
description: Mapped[str | None] = mapped_column(Text)
|
||||||
for_group: Mapped[int | None] = mapped_column(default=None)
|
for_group: Mapped[int | None] = mapped_column(default=None)
|
||||||
is_active: Mapped[bool] = mapped_column(default=True)
|
is_active: Mapped[bool] = mapped_column(default=True)
|
||||||
|
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())
|
||||||
|
|
||||||
questions: Mapped[list["Question"]] = relationship(
|
questions: Mapped[list["Question"]] = relationship(
|
||||||
back_populates="test",
|
back_populates="test",
|
||||||
@@ -46,6 +48,7 @@ class Test(Base):
|
|||||||
order_by="Question.position"
|
order_by="Question.position"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Question(Base):
|
class Question(Base):
|
||||||
__tablename__ = "questions"
|
__tablename__ = "questions"
|
||||||
@@ -63,6 +66,7 @@ class Question(Base):
|
|||||||
cascade="all, delete-orphan"
|
cascade="all, delete-orphan"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Option(Base):
|
class Option(Base):
|
||||||
__tablename__ = "options"
|
__tablename__ = "options"
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
from typing import final
|
||||||
|
from sqlalchemy import func, select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
|
from trudex.domain.schemas import Option, Question, Test
|
||||||
|
from trudex.infrastructure.database.dao.option import OptionDAO
|
||||||
|
from trudex.infrastructure.database.dao.question import QuestionDAO
|
||||||
|
from trudex.infrastructure.database.dao.test import TestDAO
|
||||||
|
from trudex.infrastructure.database.dto.option import OptionDTO
|
||||||
|
from trudex.infrastructure.database.dto.question import QuestionDTO
|
||||||
|
from trudex.infrastructure.database.dto.test import TestDTO
|
||||||
|
from trudex.infrastructure.database.models import (
|
||||||
|
Option as OptionModel,
|
||||||
|
Question as QuestionModel,
|
||||||
|
Test as TestModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class TestRepository:
|
||||||
|
def __init__(self, session: AsyncSession) -> None:
|
||||||
|
self.session = session
|
||||||
|
self.test_dao = TestDAO(session)
|
||||||
|
self.question_dao = QuestionDAO(session)
|
||||||
|
self.option_dao = OptionDAO(session)
|
||||||
|
|
||||||
|
async def get_active_tests(self) -> list[Test]:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(TestModel).where(TestModel.is_active == True)
|
||||||
|
)
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [TestDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def get_tests_by_group(self, group: int) -> list[Test]:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(TestModel).where(TestModel.for_group == group)
|
||||||
|
)
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [TestDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def get_active_tests_by_group(self, group: int) -> list[Test]:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(TestModel)
|
||||||
|
.where(TestModel.for_group == group)
|
||||||
|
.where(TestModel.is_active == True)
|
||||||
|
)
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [TestDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def get_test_with_questions(self, test_id: int) -> tuple[Test | None, list[Question]]:
|
||||||
|
test = await self.test_dao.get_by_id(test_id)
|
||||||
|
if not test:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(TestModel)
|
||||||
|
.where(TestModel.id == test_id)
|
||||||
|
.options(selectinload(TestModel.questions))
|
||||||
|
)
|
||||||
|
test_model = result.scalar_one_or_none()
|
||||||
|
if not test_model:
|
||||||
|
return test, []
|
||||||
|
|
||||||
|
questions = [QuestionDTO(q).to_domain() for q in sorted(test_model.questions, key=lambda x: x.position)]
|
||||||
|
return test, questions
|
||||||
|
|
||||||
|
async def get_question_with_options(self, question_id: int) -> tuple[Question | None, list[Option]]:
|
||||||
|
question = await self.question_dao.get_by_id(question_id)
|
||||||
|
if not question:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(QuestionModel)
|
||||||
|
.where(QuestionModel.id == question_id)
|
||||||
|
.options(selectinload(QuestionModel.options))
|
||||||
|
)
|
||||||
|
question_model = result.scalar_one_or_none()
|
||||||
|
if not question_model:
|
||||||
|
return question, []
|
||||||
|
|
||||||
|
options = [OptionDTO(o).to_domain() for o in question_model.options]
|
||||||
|
return question, options
|
||||||
|
|
||||||
|
async def get_correct_options_for_question(self, question_id: int) -> list[Option]:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(OptionModel)
|
||||||
|
.where(OptionModel.question_id == question_id)
|
||||||
|
.where(OptionModel.is_correct == True)
|
||||||
|
)
|
||||||
|
models = list(result.scalars().all())
|
||||||
|
return [OptionDTO(model).to_domain() for model in models]
|
||||||
|
|
||||||
|
async def get_full_test(self, test_id: int) -> tuple[Test | None, list[tuple[Question, list[Option]]]]:
|
||||||
|
test = await self.test_dao.get_by_id(test_id)
|
||||||
|
if not test:
|
||||||
|
return None, []
|
||||||
|
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(TestModel)
|
||||||
|
.where(TestModel.id == test_id)
|
||||||
|
.options(
|
||||||
|
selectinload(TestModel.questions).selectinload(QuestionModel.options)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
test_model = result.scalar_one_or_none()
|
||||||
|
if not test_model:
|
||||||
|
return test, []
|
||||||
|
|
||||||
|
questions_with_options: list[tuple[Question, list[Option]]] = []
|
||||||
|
for question_model in sorted(test_model.questions, key=lambda x: x.position):
|
||||||
|
question = QuestionDTO(question_model).to_domain()
|
||||||
|
options = [OptionDTO(o).to_domain() for o in question_model.options]
|
||||||
|
questions_with_options.append((question, options))
|
||||||
|
|
||||||
|
return test, questions_with_options
|
||||||
|
|
||||||
|
async def count_questions_in_test(self, test_id: int) -> int:
|
||||||
|
result = await self.session.execute(
|
||||||
|
select(func.count(QuestionModel.id))
|
||||||
|
.where(QuestionModel.test_id == test_id)
|
||||||
|
)
|
||||||
|
count = result.scalar_one()
|
||||||
|
return count
|
||||||
|
|
||||||
|
async def duplicate_test(self, test_id: int, new_title: str) -> Test | None:
|
||||||
|
test, questions_with_options = await self.get_full_test(test_id)
|
||||||
|
if not test:
|
||||||
|
return None
|
||||||
|
|
||||||
|
new_test = await self.test_dao.create(
|
||||||
|
title=new_title,
|
||||||
|
description=test.description,
|
||||||
|
for_group=test.for_group,
|
||||||
|
is_active=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for question, options in questions_with_options:
|
||||||
|
new_question = await self.question_dao.create(
|
||||||
|
test_id=new_test.id,
|
||||||
|
text=question.text,
|
||||||
|
position=question.position,
|
||||||
|
question_type=question.question_type,
|
||||||
|
tg_file_id=question.tg_file_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
_ = await self.option_dao.create(
|
||||||
|
question_id=new_question.id,
|
||||||
|
text=option.text,
|
||||||
|
is_correct=option.is_correct,
|
||||||
|
explanation=option.explanation,
|
||||||
|
)
|
||||||
|
|
||||||
|
return new_test
|
||||||
Reference in New Issue
Block a user