This commit is contained in:
2026-01-02 21:06:58 +03:00
parent 9613ecee54
commit aeeaee4add
24 changed files with 320 additions and 146 deletions
+26 -13
View File
@@ -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
@@ -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
@@ -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)
@@ -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:
@@ -13,6 +13,7 @@ class AdminUsersSG(StatesGroup):
class AdminTestsSG(StatesGroup):
tests_list = State()
test_detail = State()
class AdminBroadcastSG(StatesGroup):
@@ -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"<b>📝 Информация о тесте</b>\n\n"
f"<b>Название:</b> {test.title}\n"
f"<b>Описание:</b> {test.description or ''}\n\n"
f"<b>Статус:</b> {status}\n"
f"<b>Вопросов:</b> {questions_count}\n"
f"{password_str}\n"
f"{expires_str}\n"
f"{group_str}\n\n"
f"<b>Создан:</b> {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,
),
)
@@ -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
@@ -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
@@ -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", [])
@@ -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)
@@ -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:
@@ -14,6 +14,7 @@ class CreatorUsersSG(StatesGroup):
class CreatorTestsSG(StatesGroup):
tests_list = State()
test_detail = State()
class CreatorBroadcastSG(StatesGroup):
@@ -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"<b>📝 Информация о тесте</b>\n\n"
f"<b>Название:</b> {test.title}\n"
f"<b>Описание:</b> {test.description or ''}\n\n"
f"<b>Статус:</b> {status}\n"
f"<b>Вопросов:</b> {questions_count}\n"
f"{password_str}\n"
f"{expires_str}\n"
f"{group_str}\n\n"
f"<b>Создан:</b> {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,
),
)
@@ -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("Ошибка: пользователь не выбран")
+2 -2
View File
@@ -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()
@@ -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))
@@ -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
@@ -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:
+2 -1
View File
@@ -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
@@ -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"]
@@ -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
@@ -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(
+2 -1
View File
@@ -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
@@ -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