diff --git a/src/trudex/application/__main__.py b/src/trudex/application/__main__.py
index b76d899..054d2ce 100644
--- a/src/trudex/application/__main__.py
+++ b/src/trudex/application/__main__.py
@@ -8,22 +8,35 @@ from aiogram_dialog import setup_dialogs
from dishka import make_async_container
from dishka.integrations.aiogram import setup_dishka
-from trudex.application.bot.admin_dialogs.broadcast import broadcast_dialog as admin_broadcast_dialog
-from trudex.application.bot.admin_dialogs.groups import groups_dialog as admin_groups_dialog
+from trudex.application.bot.admin_dialogs.broadcast import \
+ broadcast_dialog as admin_broadcast_dialog
+from trudex.application.bot.admin_dialogs.groups import \
+ groups_dialog as admin_groups_dialog
from trudex.application.bot.admin_dialogs.main_menu import admin_menu_dialog
-from trudex.application.bot.admin_dialogs.tests import tests_dialog as admin_tests_dialog
-from trudex.application.bot.admin_dialogs.users import users_dialog as admin_users_dialog
-from trudex.application.bot.creator_dialogs.broadcast import broadcast_dialog as creator_broadcast_dialog
-from trudex.application.bot.creator_dialogs.create_test import create_test_dialog
-from trudex.application.bot.creator_dialogs.groups import groups_dialog as creator_groups_dialog
-from trudex.application.bot.creator_dialogs.main_menu import creator_menu_dialog
-from trudex.application.bot.creator_dialogs.tests import tests_dialog as creator_tests_dialog
-from trudex.application.bot.creator_dialogs.users import users_dialog as creator_users_dialog
+from trudex.application.bot.admin_dialogs.tests import \
+ tests_dialog as admin_tests_dialog
+from trudex.application.bot.admin_dialogs.users import \
+ users_dialog as admin_users_dialog
+from trudex.application.bot.creator_dialogs.broadcast import \
+ broadcast_dialog as creator_broadcast_dialog
+from trudex.application.bot.creator_dialogs.create_test import \
+ create_test_dialog
+from trudex.application.bot.creator_dialogs.groups import \
+ groups_dialog as creator_groups_dialog
+from trudex.application.bot.creator_dialogs.main_menu import \
+ creator_menu_dialog
+from trudex.application.bot.creator_dialogs.tests import \
+ tests_dialog as creator_tests_dialog
+from trudex.application.bot.creator_dialogs.users import \
+ users_dialog as creator_users_dialog
from trudex.application.bot.handlers import router
-from trudex.application.bot.middlewares.reject_not_admin import RejectNotAdminMiddleware
-from trudex.application.bot.middlewares.reject_not_creator import RejectNotCreatorMiddleware
+from trudex.application.bot.middlewares.reject_not_admin import \
+ RejectNotAdminMiddleware
+from trudex.application.bot.middlewares.reject_not_creator import \
+ RejectNotCreatorMiddleware
from trudex.application.bot.user_dialogs.main_menu import user_menu_dialog
-from trudex.application.bot.user_dialogs.registration import registration_dialog
+from trudex.application.bot.user_dialogs.registration import \
+ registration_dialog
from trudex.infrastructure.database.repo.user import UserRepository
from trudex.infrastructure.di import DatabaseProvider
from trudex.infrastructure.utils.bot_commands import setup_bot_commands
diff --git a/src/trudex/application/bot/admin_dialogs/broadcast.py b/src/trudex/application/bot/admin_dialogs/broadcast.py
index 69f9e8b..937ccfd 100644
--- a/src/trudex/application/bot/admin_dialogs/broadcast.py
+++ b/src/trudex/application/bot/admin_dialogs/broadcast.py
@@ -1,12 +1,13 @@
from aiogram.types import CallbackQuery, Message
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
from aiogram_dialog.widgets.kbd import Button, Row
from aiogram_dialog.widgets.text import Const
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.admin_dialogs.states import AdminBroadcastSG, AdminMenuSG
+from trudex.application.bot.admin_dialogs.states import (AdminBroadcastSG,
+ AdminMenuSG)
from trudex.infrastructure.database.dao.user import UserDAO
from trudex.infrastructure.utils.broadcast import broadcast_message
diff --git a/src/trudex/application/bot/admin_dialogs/groups.py b/src/trudex/application/bot/admin_dialogs/groups.py
index 821bc5b..4d62d10 100644
--- a/src/trudex/application/bot/admin_dialogs/groups.py
+++ b/src/trudex/application/bot/admin_dialogs/groups.py
@@ -1,11 +1,14 @@
from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Column, Row, ScrollingGroup, Select
+from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
+ Select)
from aiogram_dialog.widgets.text import Const, Format
-from dishka.integrations.aiogram import CONTAINER_NAME
+from dishka import FromDishka
+from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.admin_dialogs.states import AdminGroupsSG, AdminMenuSG
+from trudex.application.bot.admin_dialogs.states import (AdminGroupsSG,
+ AdminMenuSG)
from trudex.infrastructure.database.dao.group import GroupDAO
@@ -13,10 +16,8 @@ async def on_group_click(_callback: CallbackQuery, _widget, _manager: DialogMana
await _callback.answer("ℹ️ Для удаления используйте кнопку 'Удалить группу'")
-async def get_groups_data(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def get_groups_data(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
success_message = dialog_manager.dialog_data.pop("success_message", None)
@@ -45,7 +46,8 @@ async def on_back_to_menu(_callback: CallbackQuery, _button: Button, manager: Di
await manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
-async def on_group_number_input(message: Message, _widget: MessageInput, manager: DialogManager):
+@inject
+async def on_group_number_input(message: Message, _widget: MessageInput, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
if not message.text:
await message.answer("❌ Номер группы не может быть пустым")
return
@@ -62,9 +64,6 @@ async def on_group_number_input(message: Message, _widget: MessageInput, manager
await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)")
return
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
existing = await group_dao.get_by_number(number)
if existing:
await message.answer(f"❌ Группа с номером {number} уже существует")
@@ -84,10 +83,8 @@ async def on_cancel_add(_callback: CallbackQuery, _button: Button, manager: Dial
await manager.switch_to(AdminGroupsSG.groups_list)
-async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def get_delete_groups_data(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
return {
@@ -96,10 +93,8 @@ async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs):
}
-async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str):
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, group_dao: FromDishka[GroupDAO]):
group = await group_dao.get_by_id(int(item_id))
if not group:
await _callback.answer("❌ Группа не найдена", show_alert=True)
@@ -118,10 +113,8 @@ async def get_delete_confirm_data(dialog_manager: DialogManager, **_kwargs):
}
-async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
group_id = manager.dialog_data.get("delete_group_id")
await group_dao.delete(group_id)
diff --git a/src/trudex/application/bot/admin_dialogs/main_menu.py b/src/trudex/application/bot/admin_dialogs/main_menu.py
index 007c039..9f0c52f 100644
--- a/src/trudex/application/bot/admin_dialogs/main_menu.py
+++ b/src/trudex/application/bot/admin_dialogs/main_menu.py
@@ -1,9 +1,13 @@
from aiogram.types import CallbackQuery
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.kbd import Button, Column
from aiogram_dialog.widgets.text import Const
-from trudex.application.bot.admin_dialogs.states import AdminMenuSG, AdminUsersSG, AdminTestsSG, AdminBroadcastSG, AdminGroupsSG
+from trudex.application.bot.admin_dialogs.states import (AdminBroadcastSG,
+ AdminGroupsSG,
+ AdminMenuSG,
+ AdminTestsSG,
+ AdminUsersSG)
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
diff --git a/src/trudex/application/bot/admin_dialogs/states.py b/src/trudex/application/bot/admin_dialogs/states.py
index 312340b..71bd8f1 100644
--- a/src/trudex/application/bot/admin_dialogs/states.py
+++ b/src/trudex/application/bot/admin_dialogs/states.py
@@ -13,6 +13,7 @@ class AdminUsersSG(StatesGroup):
class AdminTestsSG(StatesGroup):
tests_list = State()
+ test_detail = State()
class AdminBroadcastSG(StatesGroup):
diff --git a/src/trudex/application/bot/admin_dialogs/tests.py b/src/trudex/application/bot/admin_dialogs/tests.py
index eae7fe9..1cf6a51 100644
--- a/src/trudex/application/bot/admin_dialogs/tests.py
+++ b/src/trudex/application/bot/admin_dialogs/tests.py
@@ -1,12 +1,15 @@
from aiogram.types import CallbackQuery
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
-from aiogram_dialog.widgets.kbd import Button, Column, ScrollingGroup, Select
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
+from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
+ Select)
from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.admin_dialogs.states import AdminTestsSG, AdminMenuSG
+from trudex.application.bot.admin_dialogs.states import (AdminMenuSG,
+ AdminTestsSG)
from trudex.infrastructure.database.dao.test import TestDAO
+from trudex.infrastructure.database.repo.test import TestRepository
@inject
@@ -24,7 +27,74 @@ async def get_tests_data(test_dao: FromDishka[TestDAO], **_kwargs):
async def on_test_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
manager.dialog_data["selected_test_id"] = int(item_id)
- await _callback.answer("Тест выбран")
+ await manager.switch_to(AdminTestsSG.test_detail)
+
+
+@inject
+async def get_test_detail(test_dao: FromDishka[TestDAO], test_repo: FromDishka[TestRepository], dialog_manager: DialogManager, **_kwargs):
+ test_id = dialog_manager.dialog_data.get("selected_test_id")
+
+ if not test_id:
+ return {
+ "test_info": "Тест не найден",
+ "is_active": False,
+ "button_text": "◀️ Назад",
+ }
+
+ test = await test_dao.get_by_id(test_id)
+ questions_count = await test_repo.count_questions_in_test(test_id)
+
+ if not test:
+ return {
+ "test_info": "Тест не найден",
+ "is_active": False,
+ "button_text": "◀️ Назад",
+ }
+
+ status = "🟢 Активен" if test.is_active else "🔴 Деактивирован"
+ password_str = f"🔒 {test.password}" if test.password else "🔓 Без пароля"
+ expires_str = test.expires_at.strftime("%d.%m.%Y %H:%M") if test.expires_at else "♾️ Без срока"
+ group_str = f"🎓 Группа {test.for_group}" if test.for_group else "👥 Для всех"
+
+ test_info = (
+ f"📝 Информация о тесте\n\n"
+ f"Название: {test.title}\n"
+ f"Описание: {test.description or '—'}\n\n"
+ f"Статус: {status}\n"
+ f"Вопросов: {questions_count}\n"
+ f"{password_str}\n"
+ f"{expires_str}\n"
+ f"{group_str}\n\n"
+ f"Создан: {test.created_at.strftime('%d.%m.%Y %H:%M') if test.created_at else '—'}"
+ )
+
+ button_text = "🔴 Деактивировать" if test.is_active else "🟢 Активировать"
+
+ return {
+ "test_info": test_info,
+ "is_active": test.is_active,
+ "button_text": button_text,
+ }
+
+
+@inject
+async def on_toggle_active(_callback: CallbackQuery, _button: Button, manager: DialogManager, test_dao: FromDishka[TestDAO]):
+ test_id = manager.dialog_data.get("selected_test_id")
+ if not test_id:
+ await _callback.answer("❌ Тест не найден")
+ return
+
+ test = await test_dao.get_by_id(test_id)
+
+ if test:
+ await test_dao.update(test_id, is_active=not test.is_active)
+ action = "деактивирован" if test.is_active else "активирован"
+ await _callback.answer(f"✅ Тест {action}")
+ await manager.switch_to(AdminTestsSG.test_detail)
+
+
+async def on_back_to_list(_callback: CallbackQuery, _button: Button, manager: DialogManager):
+ await manager.switch_to(AdminTestsSG.tests_list)
async def on_add_test_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager):
@@ -57,4 +127,17 @@ tests_dialog = Dialog(
state=AdminTestsSG.tests_list,
getter=get_tests_data,
),
+ Window(
+ Format("{test_info}"),
+ Row(
+ Button(
+ Format("{button_text}"),
+ id="toggle_active",
+ on_click=on_toggle_active
+ ),
+ Button(Const("◀️ Назад"), id="back", on_click=on_back_to_list),
+ ),
+ state=AdminTestsSG.test_detail,
+ getter=get_test_detail,
+ ),
)
diff --git a/src/trudex/application/bot/admin_dialogs/users.py b/src/trudex/application/bot/admin_dialogs/users.py
index f0d3cf8..a9c2f20 100644
--- a/src/trudex/application/bot/admin_dialogs/users.py
+++ b/src/trudex/application/bot/admin_dialogs/users.py
@@ -1,13 +1,14 @@
from aiogram.types import CallbackQuery, Message
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Column, ScrollingGroup, Select, SwitchTo
+from aiogram_dialog.widgets.kbd import (Button, Column, ScrollingGroup, Select,
+ SwitchTo)
from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka
-from dishka.integrations.aiogram import CONTAINER_NAME
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.admin_dialogs.states import AdminUsersSG, AdminMenuSG
+from trudex.application.bot.admin_dialogs.states import (AdminMenuSG,
+ AdminUsersSG)
from trudex.infrastructure.database.dao.user import UserDAO
@@ -61,10 +62,8 @@ async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: Dial
await manager.switch_to(AdminUsersSG.users_input)
-async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- user_dao = await container.get(UserDAO)
-
+@inject
+async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager, user_dao: FromDishka[UserDAO]):
text = (message.text or "").strip()
user = None
diff --git a/src/trudex/application/bot/creator_dialogs/broadcast.py b/src/trudex/application/bot/creator_dialogs/broadcast.py
index 5e42087..5663b79 100644
--- a/src/trudex/application/bot/creator_dialogs/broadcast.py
+++ b/src/trudex/application/bot/creator_dialogs/broadcast.py
@@ -1,7 +1,7 @@
from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Row, Cancel
+from aiogram_dialog.widgets.kbd import Button, Cancel, Row
from aiogram_dialog.widgets.text import Const
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
diff --git a/src/trudex/application/bot/creator_dialogs/create_test.py b/src/trudex/application/bot/creator_dialogs/create_test.py
index 50c9cc0..bb96ce3 100644
--- a/src/trudex/application/bot/creator_dialogs/create_test.py
+++ b/src/trudex/application/bot/creator_dialogs/create_test.py
@@ -1,14 +1,16 @@
from datetime import date, datetime
from aiogram.types import CallbackQuery, ContentType, Message
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Calendar, Cancel, Column, Row, ScrollingGroup, Select
+from aiogram_dialog.widgets.kbd import (Button, Calendar, Cancel, Column, Row,
+ ScrollingGroup, Select)
from aiogram_dialog.widgets.text import Const, Format
-from dishka.integrations.aiogram import CONTAINER_NAME
+from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.creator_dialogs.states import CreateTestSG, CreatorTestsSG
+from trudex.application.bot.creator_dialogs.states import (CreateTestSG,
+ CreatorTestsSG)
from trudex.infrastructure.database.dao.group import GroupDAO
from trudex.infrastructure.database.dao.option import OptionDAO
from trudex.infrastructure.database.dao.question import QuestionDAO
@@ -52,7 +54,8 @@ async def on_description_input(message: Message, _widget: MessageInput, manager:
await manager.switch_to(CreateTestSG.input_password)
-async def on_password_input(message: Message, _widget: MessageInput, manager: DialogManager):
+@inject
+async def on_password_input(message: Message, _widget: MessageInput, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
if not message.text:
await message.answer("❌ Пароль не может быть пустым")
return
@@ -68,8 +71,6 @@ async def on_password_input(message: Message, _widget: MessageInput, manager: Di
manager.dialog_data["password"] = password
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all()
if len(groups) == 0:
@@ -79,10 +80,9 @@ async def on_password_input(message: Message, _widget: MessageInput, manager: Di
await manager.switch_to(CreateTestSG.input_expires_at)
-async def on_skip_password(_callback: CallbackQuery, _button: Button, manager: DialogManager):
+@inject
+async def on_skip_password(_callback: CallbackQuery, _button: Button, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
manager.dialog_data["password"] = None
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all()
if len(groups) == 0:
@@ -102,9 +102,8 @@ async def on_skip_expires(_callback: CallbackQuery, _button: Button, manager: Di
await manager.switch_to(CreateTestSG.input_for_group)
-async def get_groups_for_test(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
+@inject
+async def get_groups_for_test(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
return {
@@ -145,10 +144,8 @@ async def get_test_info(dialog_manager: DialogManager, **_kwargs):
}
-async def on_confirm_test(_callback: CallbackQuery, _button: Button, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- test_dao = await container.get(TestDAO)
-
+@inject
+async def on_confirm_test(_callback: CallbackQuery, _button: Button, manager: DialogManager, test_dao: FromDishka[TestDAO]):
title = manager.dialog_data.get("title")
description = manager.dialog_data.get("description")
password = manager.dialog_data.get("password")
@@ -351,12 +348,15 @@ async def get_question_preview(dialog_manager: DialogManager, **_kwargs):
return {"preview": preview}
-async def on_save_question(_callback: CallbackQuery, _button: Button, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- question_dao = await container.get(QuestionDAO)
- option_dao = await container.get(OptionDAO)
- test_repo = await container.get(TestRepository)
-
+@inject
+async def on_save_question(
+ _callback: CallbackQuery,
+ _button: Button,
+ manager: DialogManager,
+ question_dao: FromDishka[QuestionDAO],
+ option_dao: FromDishka[OptionDAO],
+ test_repo: FromDishka[TestRepository],
+):
test_id = manager.dialog_data.get("test_id")
current_question = manager.dialog_data.get("current_question", {})
current_options = manager.dialog_data.get("current_options", [])
diff --git a/src/trudex/application/bot/creator_dialogs/groups.py b/src/trudex/application/bot/creator_dialogs/groups.py
index 6f5bd97..9642616 100644
--- a/src/trudex/application/bot/creator_dialogs/groups.py
+++ b/src/trudex/application/bot/creator_dialogs/groups.py
@@ -1,11 +1,14 @@
from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Column, Row, ScrollingGroup, Select
+from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
+ Select)
from aiogram_dialog.widgets.text import Const, Format
-from dishka.integrations.aiogram import CONTAINER_NAME
+from dishka import FromDishka
+from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.creator_dialogs.states import CreatorGroupsSG
+from trudex.application.bot.creator_dialogs.states import (CreatorGroupsSG,
+ CreatorMenuSG)
from trudex.infrastructure.database.dao.group import GroupDAO
@@ -13,10 +16,8 @@ async def on_group_click(_callback: CallbackQuery, _widget, _manager: DialogMana
await _callback.answer("ℹ️ Для удаления используйте кнопку 'Удалить группу'")
-async def get_groups_data(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def get_groups_data(group_dao: FromDishka[GroupDAO], dialog_manager: DialogManager, **_kwargs):
groups = await group_dao.get_all()
success_message = dialog_manager.dialog_data.pop("success_message", None)
@@ -46,7 +47,8 @@ async def on_back_to_menu(_callback: CallbackQuery, _button: Button, manager: Di
await manager.start(CreatorMenuSG.main, mode=StartMode.RESET_STACK)
-async def on_group_number_input(message: Message, _widget: MessageInput, manager: DialogManager):
+@inject
+async def on_group_number_input(message: Message, _widget: MessageInput, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
if not message.text:
await message.answer("❌ Номер группы не может быть пустым")
return
@@ -63,9 +65,6 @@ async def on_group_number_input(message: Message, _widget: MessageInput, manager
await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)")
return
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
existing = await group_dao.get_by_number(number)
if existing:
await message.answer(f"❌ Группа с номером {number} уже существует")
@@ -85,10 +84,8 @@ async def on_cancel_add(_callback: CallbackQuery, _button: Button, manager: Dial
await manager.switch_to(CreatorGroupsSG.groups_list)
-async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def get_delete_groups_data(group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
return {
@@ -97,10 +94,8 @@ async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs):
}
-async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str):
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, group_dao: FromDishka[GroupDAO]):
group = await group_dao.get_by_id(int(item_id))
if not group:
await _callback.answer("❌ Группа не найдена", show_alert=True)
@@ -119,10 +114,8 @@ async def get_delete_confirm_data(dialog_manager: DialogManager, **_kwargs):
}
-async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
group_id = manager.dialog_data.get("delete_group_id")
await group_dao.delete(group_id)
diff --git a/src/trudex/application/bot/creator_dialogs/main_menu.py b/src/trudex/application/bot/creator_dialogs/main_menu.py
index ccede3f..3d63ee0 100644
--- a/src/trudex/application/bot/creator_dialogs/main_menu.py
+++ b/src/trudex/application/bot/creator_dialogs/main_menu.py
@@ -1,9 +1,13 @@
from aiogram.types import CallbackQuery
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.kbd import Button, Column
from aiogram_dialog.widgets.text import Const
-from trudex.application.bot.creator_dialogs.states import CreatorMenuSG, CreatorUsersSG, CreatorTestsSG, CreatorBroadcastSG, CreatorGroupsSG
+from trudex.application.bot.creator_dialogs.states import (CreatorBroadcastSG,
+ CreatorGroupsSG,
+ CreatorMenuSG,
+ CreatorTestsSG,
+ CreatorUsersSG)
async def on_tests_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
diff --git a/src/trudex/application/bot/creator_dialogs/states.py b/src/trudex/application/bot/creator_dialogs/states.py
index 876135f..f6744a4 100644
--- a/src/trudex/application/bot/creator_dialogs/states.py
+++ b/src/trudex/application/bot/creator_dialogs/states.py
@@ -14,6 +14,7 @@ class CreatorUsersSG(StatesGroup):
class CreatorTestsSG(StatesGroup):
tests_list = State()
+ test_detail = State()
class CreatorBroadcastSG(StatesGroup):
diff --git a/src/trudex/application/bot/creator_dialogs/tests.py b/src/trudex/application/bot/creator_dialogs/tests.py
index 67d29e9..9c6a63d 100644
--- a/src/trudex/application/bot/creator_dialogs/tests.py
+++ b/src/trudex/application/bot/creator_dialogs/tests.py
@@ -1,12 +1,16 @@
from aiogram.types import CallbackQuery
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
-from aiogram_dialog.widgets.kbd import Button, Column, ScrollingGroup, Select
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
+from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
+ Select)
from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.creator_dialogs.states import CreatorTestsSG, CreatorMenuSG, CreateTestSG
+from trudex.application.bot.creator_dialogs.states import (CreateTestSG,
+ CreatorMenuSG,
+ CreatorTestsSG)
from trudex.infrastructure.database.dao.test import TestDAO
+from trudex.infrastructure.database.repo.test import TestRepository
@inject
@@ -24,7 +28,74 @@ async def get_tests_data(test_dao: FromDishka[TestDAO], **_kwargs):
async def on_test_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
manager.dialog_data["selected_test_id"] = int(item_id)
- await _callback.answer("Тест выбран")
+ await manager.switch_to(CreatorTestsSG.test_detail)
+
+
+@inject
+async def get_test_detail(test_dao: FromDishka[TestDAO], test_repo: FromDishka[TestRepository], dialog_manager: DialogManager, **_kwargs):
+ test_id = dialog_manager.dialog_data.get("selected_test_id")
+
+ if not test_id:
+ return {
+ "test_info": "Тест не найден",
+ "is_active": False,
+ "button_text": "◀️ Назад",
+ }
+
+ test = await test_dao.get_by_id(test_id)
+ questions_count = await test_repo.count_questions_in_test(test_id)
+
+ if not test:
+ return {
+ "test_info": "Тест не найден",
+ "is_active": False,
+ "button_text": "◀️ Назад",
+ }
+
+ status = "🟢 Активен" if test.is_active else "🔴 Деактивирован"
+ password_str = f"🔒 {test.password}" if test.password else "🔓 Без пароля"
+ expires_str = test.expires_at.strftime("%d.%m.%Y %H:%M") if test.expires_at else "♾️ Без срока"
+ group_str = f"🎓 Группа {test.for_group}" if test.for_group else "👥 Для всех"
+
+ test_info = (
+ f"📝 Информация о тесте\n\n"
+ f"Название: {test.title}\n"
+ f"Описание: {test.description or '—'}\n\n"
+ f"Статус: {status}\n"
+ f"Вопросов: {questions_count}\n"
+ f"{password_str}\n"
+ f"{expires_str}\n"
+ f"{group_str}\n\n"
+ f"Создан: {test.created_at.strftime('%d.%m.%Y %H:%M') if test.created_at else '—'}"
+ )
+
+ button_text = "🔴 Деактивировать" if test.is_active else "🟢 Активировать"
+
+ return {
+ "test_info": test_info,
+ "is_active": test.is_active,
+ "button_text": button_text,
+ }
+
+
+@inject
+async def on_toggle_active(_callback: CallbackQuery, _button: Button, manager: DialogManager, test_dao: FromDishka[TestDAO]):
+ test_id = manager.dialog_data.get("selected_test_id")
+ if not test_id:
+ await _callback.answer("❌ Тест не найден")
+ return
+
+ test = await test_dao.get_by_id(test_id)
+
+ if test:
+ await test_dao.update(test_id, is_active=not test.is_active)
+ action = "деактивирован" if test.is_active else "активирован"
+ await _callback.answer(f"✅ Тест {action}")
+ await manager.switch_to(CreatorTestsSG.test_detail)
+
+
+async def on_back_to_list(_callback: CallbackQuery, _button: Button, manager: DialogManager):
+ await manager.switch_to(CreatorTestsSG.tests_list)
async def on_add_test_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager):
@@ -57,4 +128,17 @@ tests_dialog = Dialog(
state=CreatorTestsSG.tests_list,
getter=get_tests_data,
),
+ Window(
+ Format("{test_info}"),
+ Row(
+ Button(
+ Format("{button_text}"),
+ id="toggle_active",
+ on_click=on_toggle_active
+ ),
+ Button(Const("◀️ Назад"), id="back", on_click=on_back_to_list),
+ ),
+ state=CreatorTestsSG.test_detail,
+ getter=get_test_detail,
+ ),
)
diff --git a/src/trudex/application/bot/creator_dialogs/users.py b/src/trudex/application/bot/creator_dialogs/users.py
index 6702510..a42b207 100644
--- a/src/trudex/application/bot/creator_dialogs/users.py
+++ b/src/trudex/application/bot/creator_dialogs/users.py
@@ -1,13 +1,14 @@
from aiogram.types import CallbackQuery, Message
-from aiogram_dialog import Dialog, DialogManager, Window, StartMode
+from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput
-from aiogram_dialog.widgets.kbd import Button, Column, Row, ScrollingGroup, Select, SwitchTo
+from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
+ Select, SwitchTo)
from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka
-from dishka.integrations.aiogram import CONTAINER_NAME
from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.creator_dialogs.states import CreatorUsersSG, CreatorMenuSG
+from trudex.application.bot.creator_dialogs.states import (CreatorMenuSG,
+ CreatorUsersSG)
from trudex.infrastructure.database.dao.user import UserDAO
@@ -81,10 +82,8 @@ async def on_input_mode(_callback: CallbackQuery, _button: Button, manager: Dial
await manager.switch_to(CreatorUsersSG.users_input)
-async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- user_dao = await container.get(UserDAO)
-
+@inject
+async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager, user_dao: FromDishka[UserDAO]):
text = (message.text or "").strip()
user = None
@@ -107,10 +106,8 @@ async def on_make_admin_clicked(_callback: CallbackQuery, _button: Button, manag
await manager.switch_to(CreatorUsersSG.make_admin_confirm)
-async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager):
- container = manager.middleware_data[CONTAINER_NAME]
- user_dao = await container.get(UserDAO)
-
+@inject
+async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager, user_dao: FromDishka[UserDAO]):
user_id = manager.dialog_data.get("selected_user_id")
if not user_id:
await _callback.answer("Ошибка: пользователь не выбран")
diff --git a/src/trudex/application/bot/handlers.py b/src/trudex/application/bot/handlers.py
index 5455a9c..c19724e 100644
--- a/src/trudex/application/bot/handlers.py
+++ b/src/trudex/application/bot/handlers.py
@@ -7,11 +7,11 @@ from dishka.integrations.aiogram import FromDishka
from trudex.application.bot.admin_dialogs.states import AdminMenuSG
from trudex.application.bot.creator_dialogs.states import CreatorMenuSG
-from trudex.application.bot.user_dialogs.states import UserMenuSG, UserRegistrationSG
+from trudex.application.bot.user_dialogs.states import (UserMenuSG,
+ UserRegistrationSG)
from trudex.infrastructure.database.dao.group import GroupDAO
from trudex.infrastructure.database.dao.user import UserDAO
-
router = Router()
diff --git a/src/trudex/application/bot/user_dialogs/registration.py b/src/trudex/application/bot/user_dialogs/registration.py
index f597895..d1d77f0 100644
--- a/src/trudex/application/bot/user_dialogs/registration.py
+++ b/src/trudex/application/bot/user_dialogs/registration.py
@@ -2,17 +2,17 @@ from aiogram.types import CallbackQuery
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.kbd import ScrollingGroup, Select
from aiogram_dialog.widgets.text import Const, Format
-from dishka.integrations.aiogram import CONTAINER_NAME
+from dishka import FromDishka
+from dishka.integrations.aiogram_dialog import inject
-from trudex.application.bot.user_dialogs.states import UserMenuSG, UserRegistrationSG
+from trudex.application.bot.user_dialogs.states import (UserMenuSG,
+ UserRegistrationSG)
from trudex.infrastructure.database.dao.group import GroupDAO
from trudex.infrastructure.database.dao.user import UserDAO
-async def get_groups_for_registration(dialog_manager: DialogManager, **_kwargs):
- container = dialog_manager.middleware_data[CONTAINER_NAME]
- group_dao = await container.get(GroupDAO)
-
+@inject
+async def get_groups_for_registration(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
groups = await group_dao.get_all()
return {
@@ -20,10 +20,8 @@ async def get_groups_for_registration(dialog_manager: DialogManager, **_kwargs):
}
-async def on_group_selected(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str):
- container = manager.middleware_data[CONTAINER_NAME]
- user_dao = await container.get(UserDAO)
-
+@inject
+async def on_group_selected(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, user_dao: FromDishka[UserDAO]):
user_id = manager.start_data.get("user_id")
await user_dao.update(user_id=user_id, group=int(item_id))
diff --git a/src/trudex/infrastructure/database/dao/__init__.py b/src/trudex/infrastructure/database/dao/__init__.py
index ecf61f2..9d85bbf 100644
--- a/src/trudex/infrastructure/database/dao/__init__.py
+++ b/src/trudex/infrastructure/database/dao/__init__.py
@@ -1,5 +1,5 @@
+from .option import OptionDAO as OptionDAO
+from .question import QuestionDAO as QuestionDAO
from .test import TestDAO as TestDAO
from .user import UserDAO as UserDAO
-from .question import QuestionDAO as QuestionDAO
-from .option import OptionDAO as OptionDAO
\ No newline at end of file
diff --git a/src/trudex/infrastructure/database/dto/test_attempt.py b/src/trudex/infrastructure/database/dto/test_attempt.py
index 786eb38..9fb3255 100644
--- a/src/trudex/infrastructure/database/dto/test_attempt.py
+++ b/src/trudex/infrastructure/database/dto/test_attempt.py
@@ -1,5 +1,6 @@
from trudex.domain.schemas import TestAttempt as DomainTestAttempt
-from trudex.infrastructure.database.models import TestAttempt as TestAttemptModel
+from trudex.infrastructure.database.models import \
+ TestAttempt as TestAttemptModel
class TestAttemptDTO:
diff --git a/src/trudex/infrastructure/database/models.py b/src/trudex/infrastructure/database/models.py
index bac500f..2d03310 100644
--- a/src/trudex/infrastructure/database/models.py
+++ b/src/trudex/infrastructure/database/models.py
@@ -2,7 +2,8 @@ from datetime import datetime
from enum import Enum
from typing import final
-from sqlalchemy import BigInteger, CheckConstraint, ForeignKey, Integer, String, Text, func
+from sqlalchemy import (BigInteger, CheckConstraint, ForeignKey, Integer,
+ String, Text, func)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
diff --git a/src/trudex/infrastructure/database/repo/__init__.py b/src/trudex/infrastructure/database/repo/__init__.py
index 3d25dad..1f6a9b8 100644
--- a/src/trudex/infrastructure/database/repo/__init__.py
+++ b/src/trudex/infrastructure/database/repo/__init__.py
@@ -1,5 +1,6 @@
from trudex.infrastructure.database.repo.test import TestRepository
-from trudex.infrastructure.database.repo.test_attempt import TestAttemptRepository
+from trudex.infrastructure.database.repo.test_attempt import \
+ TestAttemptRepository
from trudex.infrastructure.database.repo.user import UserRepository
__all__ = ["TestRepository", "TestAttemptRepository", "UserRepository"]
diff --git a/src/trudex/infrastructure/database/repo/test.py b/src/trudex/infrastructure/database/repo/test.py
index fc36ad5..fba422b 100644
--- a/src/trudex/infrastructure/database/repo/test.py
+++ b/src/trudex/infrastructure/database/repo/test.py
@@ -11,11 +11,9 @@ 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,
-)
+from trudex.infrastructure.database.models import Option as OptionModel
+from trudex.infrastructure.database.models import Question as QuestionModel
+from trudex.infrastructure.database.models import Test as TestModel
@final
diff --git a/src/trudex/infrastructure/database/repo/test_attempt.py b/src/trudex/infrastructure/database/repo/test_attempt.py
index d6043cb..476cb23 100644
--- a/src/trudex/infrastructure/database/repo/test_attempt.py
+++ b/src/trudex/infrastructure/database/repo/test_attempt.py
@@ -10,10 +10,9 @@ from trudex.infrastructure.database.dao.test_attempt import TestAttemptDAO
from trudex.infrastructure.database.dao.user_answer import UserAnswerDAO
from trudex.infrastructure.database.dto.test_attempt import TestAttemptDTO
from trudex.infrastructure.database.dto.user_answer import UserAnswerDTO
-from trudex.infrastructure.database.models import (
- TestAttempt as TestAttemptModel,
- UserAnswer as UserAnswerModel,
-)
+from trudex.infrastructure.database.models import \
+ TestAttempt as TestAttemptModel
+from trudex.infrastructure.database.models import UserAnswer as UserAnswerModel
@final
@@ -177,7 +176,8 @@ class TestAttemptRepository:
}
async def get_most_difficult_questions(self, test_id: int, limit: int = 10) -> list[tuple[int, float]]:
- from trudex.infrastructure.database.models import Question as QuestionModel
+ from trudex.infrastructure.database.models import \
+ Question as QuestionModel
result = await self.session.execute(
select(
diff --git a/src/trudex/infrastructure/di.py b/src/trudex/infrastructure/di.py
index 06756ed..536735d 100644
--- a/src/trudex/infrastructure/di.py
+++ b/src/trudex/infrastructure/di.py
@@ -12,7 +12,8 @@ from trudex.infrastructure.database.dao.test_attempt import TestAttemptDAO
from trudex.infrastructure.database.dao.user import UserDAO
from trudex.infrastructure.database.dao.user_answer import UserAnswerDAO
from trudex.infrastructure.database.repo.test import TestRepository
-from trudex.infrastructure.database.repo.test_attempt import TestAttemptRepository
+from trudex.infrastructure.database.repo.test_attempt import \
+ TestAttemptRepository
from trudex.infrastructure.database.repo.user import UserRepository
from trudex.infrastructure.utils.config import Config
diff --git a/src/trudex/infrastructure/utils/bot_commands.py b/src/trudex/infrastructure/utils/bot_commands.py
index d017f64..15b73ab 100644
--- a/src/trudex/infrastructure/utils/bot_commands.py
+++ b/src/trudex/infrastructure/utils/bot_commands.py
@@ -1,5 +1,6 @@
from aiogram import Bot
-from aiogram.types import BotCommand, BotCommandScopeAllPrivateChats, BotCommandScopeChat
+from aiogram.types import (BotCommand, BotCommandScopeAllPrivateChats,
+ BotCommandScopeChat)
from trudex.infrastructure.database.repo.user import UserRepository
from trudex.infrastructure.utils.config import Config