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 import make_async_container
from dishka.integrations.aiogram import setup_dishka 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.broadcast import \
from trudex.application.bot.admin_dialogs.groups import groups_dialog as admin_groups_dialog 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.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.tests import \
from trudex.application.bot.admin_dialogs.users import users_dialog as admin_users_dialog tests_dialog as admin_tests_dialog
from trudex.application.bot.creator_dialogs.broadcast import broadcast_dialog as creator_broadcast_dialog from trudex.application.bot.admin_dialogs.users import \
from trudex.application.bot.creator_dialogs.create_test import create_test_dialog users_dialog as admin_users_dialog
from trudex.application.bot.creator_dialogs.groups import groups_dialog as creator_groups_dialog from trudex.application.bot.creator_dialogs.broadcast import \
from trudex.application.bot.creator_dialogs.main_menu import creator_menu_dialog broadcast_dialog as creator_broadcast_dialog
from trudex.application.bot.creator_dialogs.tests import tests_dialog as creator_tests_dialog from trudex.application.bot.creator_dialogs.create_test import \
from trudex.application.bot.creator_dialogs.users import users_dialog as creator_users_dialog 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.handlers import router
from trudex.application.bot.middlewares.reject_not_admin import RejectNotAdminMiddleware from trudex.application.bot.middlewares.reject_not_admin import \
from trudex.application.bot.middlewares.reject_not_creator import RejectNotCreatorMiddleware 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.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.database.repo.user import UserRepository
from trudex.infrastructure.di import DatabaseProvider from trudex.infrastructure.di import DatabaseProvider
from trudex.infrastructure.utils.bot_commands import setup_bot_commands from trudex.infrastructure.utils.bot_commands import setup_bot_commands
@@ -1,12 +1,13 @@
from aiogram.types import CallbackQuery, Message 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.input import MessageInput
from aiogram_dialog.widgets.kbd import Button, Row from aiogram_dialog.widgets.kbd import Button, Row
from aiogram_dialog.widgets.text import Const from aiogram_dialog.widgets.text import Const
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject 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.database.dao.user import UserDAO
from trudex.infrastructure.utils.broadcast import broadcast_message from trudex.infrastructure.utils.broadcast import broadcast_message
@@ -1,11 +1,14 @@
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, StartMode, Window from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput 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 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 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("ℹ️ Для удаления используйте кнопку 'Удалить группу'") await _callback.answer("ℹ️ Для удаления используйте кнопку 'Удалить группу'")
async def get_groups_data(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_groups_data(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
success_message = dialog_manager.dialog_data.pop("success_message", None) 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) 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: if not message.text:
await message.answer("❌ Номер группы не может быть пустым") await message.answer("❌ Номер группы не может быть пустым")
return return
@@ -62,9 +64,6 @@ async def on_group_number_input(message: Message, _widget: MessageInput, manager
await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)") await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)")
return return
container = manager.middleware_data[CONTAINER_NAME]
group_dao = await container.get(GroupDAO)
existing = await group_dao.get_by_number(number) existing = await group_dao.get_by_number(number)
if existing: if existing:
await message.answer(f"❌ Группа с номером {number} уже существует") 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) await manager.switch_to(AdminGroupsSG.groups_list)
async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_delete_groups_data(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
return { 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, group_dao: FromDishka[GroupDAO]):
group_dao = await container.get(GroupDAO)
group = await group_dao.get_by_id(int(item_id)) group = await group_dao.get_by_id(int(item_id))
if not group: if not group:
await _callback.answer("❌ Группа не найдена", show_alert=True) 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
group_dao = await container.get(GroupDAO)
group_id = manager.dialog_data.get("delete_group_id") group_id = manager.dialog_data.get("delete_group_id")
await group_dao.delete(group_id) await group_dao.delete(group_id)
@@ -1,9 +1,13 @@
from aiogram.types import CallbackQuery 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.kbd import Button, Column
from aiogram_dialog.widgets.text import Const 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: async def on_tests_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
@@ -13,6 +13,7 @@ class AdminUsersSG(StatesGroup):
class AdminTestsSG(StatesGroup): class AdminTestsSG(StatesGroup):
tests_list = State() tests_list = State()
test_detail = State()
class AdminBroadcastSG(StatesGroup): class AdminBroadcastSG(StatesGroup):
@@ -1,12 +1,15 @@
from aiogram.types import CallbackQuery 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, ScrollingGroup, Select from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
Select)
from aiogram_dialog.widgets.text import Const, Format from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject 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.dao.test import TestDAO
from trudex.infrastructure.database.repo.test import TestRepository
@inject @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): async def on_test_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
manager.dialog_data["selected_test_id"] = int(item_id) 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): async def on_add_test_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager):
@@ -57,4 +127,17 @@ tests_dialog = Dialog(
state=AdminTestsSG.tests_list, state=AdminTestsSG.tests_list,
getter=get_tests_data, 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.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.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 aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram import CONTAINER_NAME
from dishka.integrations.aiogram_dialog import inject 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 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) await manager.switch_to(AdminUsersSG.users_input)
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager, user_dao: FromDishka[UserDAO]):
user_dao = await container.get(UserDAO)
text = (message.text or "").strip() text = (message.text or "").strip()
user = None user = None
@@ -1,7 +1,7 @@
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, Window from aiogram_dialog import Dialog, DialogManager, Window
from aiogram_dialog.widgets.input import MessageInput 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 aiogram_dialog.widgets.text import Const
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject from dishka.integrations.aiogram_dialog import inject
@@ -1,14 +1,16 @@
from datetime import date, datetime from datetime import date, datetime
from aiogram.types import CallbackQuery, ContentType, Message 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.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 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 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.group import GroupDAO
from trudex.infrastructure.database.dao.option import OptionDAO from trudex.infrastructure.database.dao.option import OptionDAO
from trudex.infrastructure.database.dao.question import QuestionDAO 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) 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: if not message.text:
await message.answer("❌ Пароль не может быть пустым") await message.answer("❌ Пароль не может быть пустым")
return return
@@ -68,8 +71,6 @@ async def on_password_input(message: Message, _widget: MessageInput, manager: Di
manager.dialog_data["password"] = password manager.dialog_data["password"] = password
container = manager.middleware_data[CONTAINER_NAME]
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
if len(groups) == 0: 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) 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 manager.dialog_data["password"] = None
container = manager.middleware_data[CONTAINER_NAME]
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
if len(groups) == 0: 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) await manager.switch_to(CreateTestSG.input_for_group)
async def get_groups_for_test(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_groups_for_test(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
return { 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_confirm_test(_callback: CallbackQuery, _button: Button, manager: DialogManager, test_dao: FromDishka[TestDAO]):
test_dao = await container.get(TestDAO)
title = manager.dialog_data.get("title") title = manager.dialog_data.get("title")
description = manager.dialog_data.get("description") description = manager.dialog_data.get("description")
password = manager.dialog_data.get("password") password = manager.dialog_data.get("password")
@@ -351,12 +348,15 @@ async def get_question_preview(dialog_manager: DialogManager, **_kwargs):
return {"preview": preview} return {"preview": preview}
async def on_save_question(_callback: CallbackQuery, _button: Button, manager: DialogManager): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_save_question(
question_dao = await container.get(QuestionDAO) _callback: CallbackQuery,
option_dao = await container.get(OptionDAO) _button: Button,
test_repo = await container.get(TestRepository) manager: DialogManager,
question_dao: FromDishka[QuestionDAO],
option_dao: FromDishka[OptionDAO],
test_repo: FromDishka[TestRepository],
):
test_id = manager.dialog_data.get("test_id") test_id = manager.dialog_data.get("test_id")
current_question = manager.dialog_data.get("current_question", {}) current_question = manager.dialog_data.get("current_question", {})
current_options = manager.dialog_data.get("current_options", []) current_options = manager.dialog_data.get("current_options", [])
@@ -1,11 +1,14 @@
from aiogram.types import CallbackQuery, Message from aiogram.types import CallbackQuery, Message
from aiogram_dialog import Dialog, DialogManager, StartMode, Window from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.input import MessageInput 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 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 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("ℹ️ Для удаления используйте кнопку 'Удалить группу'") await _callback.answer("ℹ️ Для удаления используйте кнопку 'Удалить группу'")
async def get_groups_data(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_groups_data(group_dao: FromDishka[GroupDAO], dialog_manager: DialogManager, **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
success_message = dialog_manager.dialog_data.pop("success_message", None) 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) 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: if not message.text:
await message.answer("❌ Номер группы не может быть пустым") await message.answer("❌ Номер группы не может быть пустым")
return return
@@ -63,9 +65,6 @@ async def on_group_number_input(message: Message, _widget: MessageInput, manager
await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)") await message.answer("❌ Номер группы должен быть четырехзначным (1000-9999)")
return return
container = manager.middleware_data[CONTAINER_NAME]
group_dao = await container.get(GroupDAO)
existing = await group_dao.get_by_number(number) existing = await group_dao.get_by_number(number)
if existing: if existing:
await message.answer(f"❌ Группа с номером {number} уже существует") 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) await manager.switch_to(CreatorGroupsSG.groups_list)
async def get_delete_groups_data(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_delete_groups_data(group_dao: FromDishka[GroupDAO], **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
return { 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_select_group_to_delete(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, group_dao: FromDishka[GroupDAO]):
group_dao = await container.get(GroupDAO)
group = await group_dao.get_by_id(int(item_id)) group = await group_dao.get_by_id(int(item_id))
if not group: if not group:
await _callback.answer("❌ Группа не найдена", show_alert=True) 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_confirm_delete(_callback: CallbackQuery, _button: Button, manager: DialogManager, group_dao: FromDishka[GroupDAO]):
group_dao = await container.get(GroupDAO)
group_id = manager.dialog_data.get("delete_group_id") group_id = manager.dialog_data.get("delete_group_id")
await group_dao.delete(group_id) await group_dao.delete(group_id)
@@ -1,9 +1,13 @@
from aiogram.types import CallbackQuery 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.kbd import Button, Column
from aiogram_dialog.widgets.text import Const 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: async def on_tests_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
@@ -14,6 +14,7 @@ class CreatorUsersSG(StatesGroup):
class CreatorTestsSG(StatesGroup): class CreatorTestsSG(StatesGroup):
tests_list = State() tests_list = State()
test_detail = State()
class CreatorBroadcastSG(StatesGroup): class CreatorBroadcastSG(StatesGroup):
@@ -1,12 +1,16 @@
from aiogram.types import CallbackQuery 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, ScrollingGroup, Select from aiogram_dialog.widgets.kbd import (Button, Column, Row, ScrollingGroup,
Select)
from aiogram_dialog.widgets.text import Const, Format from aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject 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.dao.test import TestDAO
from trudex.infrastructure.database.repo.test import TestRepository
@inject @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): async def on_test_selected(_callback: CallbackQuery, _widget: Select, manager: DialogManager, item_id: str):
manager.dialog_data["selected_test_id"] = int(item_id) 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): async def on_add_test_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager):
@@ -57,4 +128,17 @@ tests_dialog = Dialog(
state=CreatorTestsSG.tests_list, state=CreatorTestsSG.tests_list,
getter=get_tests_data, 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.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.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 aiogram_dialog.widgets.text import Const, Format
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram import CONTAINER_NAME
from dishka.integrations.aiogram_dialog import inject 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 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) await manager.switch_to(CreatorUsersSG.users_input)
async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_user_input(message: Message, _widget: MessageInput, manager: DialogManager, user_dao: FromDishka[UserDAO]):
user_dao = await container.get(UserDAO)
text = (message.text or "").strip() text = (message.text or "").strip()
user = None 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) await manager.switch_to(CreatorUsersSG.make_admin_confirm)
async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_confirm_yes(_callback: CallbackQuery, _button: Button, manager: DialogManager, user_dao: FromDishka[UserDAO]):
user_dao = await container.get(UserDAO)
user_id = manager.dialog_data.get("selected_user_id") user_id = manager.dialog_data.get("selected_user_id")
if not user_id: if not user_id:
await _callback.answer("Ошибка: пользователь не выбран") 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.admin_dialogs.states import AdminMenuSG
from trudex.application.bot.creator_dialogs.states import CreatorMenuSG 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.group import GroupDAO
from trudex.infrastructure.database.dao.user import UserDAO from trudex.infrastructure.database.dao.user import UserDAO
router = Router() router = Router()
@@ -2,17 +2,17 @@ from aiogram.types import CallbackQuery
from aiogram_dialog import Dialog, DialogManager, StartMode, Window from aiogram_dialog import Dialog, DialogManager, StartMode, Window
from aiogram_dialog.widgets.kbd import ScrollingGroup, Select from aiogram_dialog.widgets.kbd import ScrollingGroup, Select
from aiogram_dialog.widgets.text import Const, Format 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.group import GroupDAO
from trudex.infrastructure.database.dao.user import UserDAO from trudex.infrastructure.database.dao.user import UserDAO
async def get_groups_for_registration(dialog_manager: DialogManager, **_kwargs): @inject
container = dialog_manager.middleware_data[CONTAINER_NAME] async def get_groups_for_registration(dialog_manager: DialogManager, group_dao: FromDishka[GroupDAO], **_kwargs):
group_dao = await container.get(GroupDAO)
groups = await group_dao.get_all() groups = await group_dao.get_all()
return { 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): @inject
container = manager.middleware_data[CONTAINER_NAME] async def on_group_selected(_callback: CallbackQuery, _widget, manager: DialogManager, item_id: str, user_dao: FromDishka[UserDAO]):
user_dao = await container.get(UserDAO)
user_id = manager.start_data.get("user_id") user_id = manager.start_data.get("user_id")
await user_dao.update(user_id=user_id, group=int(item_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 .test import TestDAO as TestDAO
from .user import UserDAO as UserDAO 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.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: class TestAttemptDTO:
+2 -1
View File
@@ -2,7 +2,8 @@ from datetime import datetime
from enum import Enum from enum import Enum
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)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship 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 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.database.repo.user import UserRepository
__all__ = ["TestRepository", "TestAttemptRepository", "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.option import OptionDTO
from trudex.infrastructure.database.dto.question import QuestionDTO from trudex.infrastructure.database.dto.question import QuestionDTO
from trudex.infrastructure.database.dto.test import TestDTO from trudex.infrastructure.database.dto.test import TestDTO
from trudex.infrastructure.database.models import ( from trudex.infrastructure.database.models import Option as OptionModel
Option as OptionModel, from trudex.infrastructure.database.models import Question as QuestionModel
Question as QuestionModel, from trudex.infrastructure.database.models import Test as TestModel
Test as TestModel,
)
@final @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.dao.user_answer import UserAnswerDAO
from trudex.infrastructure.database.dto.test_attempt import TestAttemptDTO from trudex.infrastructure.database.dto.test_attempt import TestAttemptDTO
from trudex.infrastructure.database.dto.user_answer import UserAnswerDTO from trudex.infrastructure.database.dto.user_answer import UserAnswerDTO
from trudex.infrastructure.database.models import ( from trudex.infrastructure.database.models import \
TestAttempt as TestAttemptModel, TestAttempt as TestAttemptModel
UserAnswer as UserAnswerModel, from trudex.infrastructure.database.models import UserAnswer as UserAnswerModel
)
@final @final
@@ -177,7 +176,8 @@ class TestAttemptRepository:
} }
async def get_most_difficult_questions(self, test_id: int, limit: int = 10) -> list[tuple[int, float]]: 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( result = await self.session.execute(
select( 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 import UserDAO
from trudex.infrastructure.database.dao.user_answer import UserAnswerDAO from trudex.infrastructure.database.dao.user_answer import UserAnswerDAO
from trudex.infrastructure.database.repo.test import TestRepository 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.database.repo.user import UserRepository
from trudex.infrastructure.utils.config import Config from trudex.infrastructure.utils.config import Config
@@ -1,5 +1,6 @@
from aiogram import Bot 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.database.repo.user import UserRepository
from trudex.infrastructure.utils.config import Config from trudex.infrastructure.utils.config import Config