mirror of
https://github.com/koloideal/Quizzi.git
synced 2026-06-10 10:25:28 +03:00
commit
This commit is contained in:
@@ -14,6 +14,8 @@ from trudex.application.bot.admin_dialogs.broadcast import \
|
||||
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.templates import \
|
||||
templates_dialog as admin_templates_dialog
|
||||
from trudex.application.bot.admin_dialogs.tests import \
|
||||
tests_dialog as admin_tests_dialog
|
||||
from trudex.application.bot.admin_dialogs.users import \
|
||||
@@ -26,6 +28,8 @@ 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.templates import \
|
||||
templates_dialog as creator_templates_dialog
|
||||
from trudex.application.bot.creator_dialogs.tests import \
|
||||
tests_dialog as creator_tests_dialog
|
||||
from trudex.application.bot.creator_dialogs.users import \
|
||||
@@ -72,11 +76,13 @@ async def main() -> None:
|
||||
admin_tests_dialog,
|
||||
admin_groups_dialog,
|
||||
admin_broadcast_dialog,
|
||||
admin_templates_dialog,
|
||||
creator_menu_dialog,
|
||||
creator_users_dialog,
|
||||
creator_tests_dialog,
|
||||
creator_groups_dialog,
|
||||
creator_broadcast_dialog,
|
||||
creator_templates_dialog,
|
||||
create_test_dialog,
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from aiogram_dialog.widgets.text import Const
|
||||
from trudex.application.bot.admin_dialogs.states import (AdminBroadcastSG,
|
||||
AdminGroupsSG,
|
||||
AdminMenuSG,
|
||||
AdminTemplatesSG,
|
||||
AdminTestsSG,
|
||||
AdminUsersSG)
|
||||
|
||||
@@ -26,6 +27,10 @@ async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, manage
|
||||
await manager.start(AdminBroadcastSG.broadcast_input, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_templates_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.start(AdminTemplatesSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
admin_menu_dialog = Dialog(
|
||||
Window(
|
||||
Const("🔧 <b>Админ-панель</b>\n\nВыберите раздел:"),
|
||||
@@ -34,6 +39,7 @@ admin_menu_dialog = Dialog(
|
||||
Button(Const("👥 Пользователи"), id="users", on_click=on_users_clicked),
|
||||
Button(Const("🎓 Группы"), id="groups", on_click=on_groups_clicked),
|
||||
Button(Const("📢 Рассылка"), id="broadcast", on_click=on_broadcast_clicked),
|
||||
Button(Const("📦 Шаблоны тестов"), id="templates", on_click=on_templates_clicked),
|
||||
),
|
||||
state=AdminMenuSG.main,
|
||||
),
|
||||
|
||||
@@ -5,6 +5,11 @@ class AdminMenuSG(StatesGroup):
|
||||
main = State()
|
||||
|
||||
|
||||
class AdminTemplatesSG(StatesGroup):
|
||||
main = State()
|
||||
export_list = State()
|
||||
|
||||
|
||||
class AdminUsersSG(StatesGroup):
|
||||
users_list = State()
|
||||
users_input = State()
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import json
|
||||
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery
|
||||
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
|
||||
from aiogram_dialog.widgets.kbd import Button, 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 AdminMenuSG, AdminTemplatesSG
|
||||
from trudex.infrastructure.database.dao.test import TestDAO
|
||||
from trudex.infrastructure.database.repo.test import TestRepository
|
||||
|
||||
|
||||
TEMPLATES_INFO = (
|
||||
"<b>📦 Шаблоны тестов</b>\n\n"
|
||||
"Шаблоны позволяют экспортировать и импортировать тесты в формате JSON.\n\n"
|
||||
"🔹 <b>Экспорт</b> — сохраните тест как файл для резервной копии или передачи\n"
|
||||
"🔹 <b>Импорт</b> — загрузите тест из файла\n"
|
||||
"🔹 <b>Спецификация</b> — описание формата JSON для создания тестов вручную"
|
||||
)
|
||||
|
||||
|
||||
async def on_export_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(AdminTemplatesSG.export_list)
|
||||
|
||||
|
||||
async def on_import_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||
await _callback.answer("🚧 В разработке", show_alert=True)
|
||||
|
||||
|
||||
async def on_spec_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||
await _callback.answer("🚧 В разработке", show_alert=True)
|
||||
|
||||
|
||||
async def on_back_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.start(AdminMenuSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_back_to_templates(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(AdminTemplatesSG.main)
|
||||
|
||||
|
||||
@inject
|
||||
async def get_tests_for_export(test_dao: FromDishka[TestDAO], **_kwargs):
|
||||
tests = await test_dao.get_all()
|
||||
return {
|
||||
"tests": [(f"📝 {t.title}", t.id) for t in tests],
|
||||
"count": len(tests),
|
||||
}
|
||||
|
||||
|
||||
@inject
|
||||
async def on_test_selected_for_export(
|
||||
_callback: CallbackQuery,
|
||||
_widget: Select,
|
||||
_manager: DialogManager,
|
||||
item_id: str,
|
||||
test_repo: FromDishka[TestRepository],
|
||||
):
|
||||
test_id = int(item_id)
|
||||
test, questions_with_options = await test_repo.get_full_test(test_id)
|
||||
|
||||
if not test:
|
||||
await _callback.answer("❌ Тест не найден")
|
||||
return
|
||||
|
||||
export_data: dict = {
|
||||
"title": test.title,
|
||||
"description": test.description,
|
||||
"password": test.password,
|
||||
"attempts": test.attempts,
|
||||
"for_group": test.for_group,
|
||||
"questions": [],
|
||||
}
|
||||
|
||||
questions_list: list = export_data["questions"]
|
||||
|
||||
for question, options in questions_with_options:
|
||||
question_data: dict = {
|
||||
"text": question.text,
|
||||
"question_type": question.question_type,
|
||||
}
|
||||
|
||||
if question.question_type == "input":
|
||||
correct_options = [o for o in options if o.is_correct]
|
||||
if correct_options:
|
||||
question_data["correct_answer"] = correct_options[0].text
|
||||
else:
|
||||
question_data["options"] = [
|
||||
{"text": o.text, "is_correct": o.is_correct}
|
||||
for o in options
|
||||
]
|
||||
|
||||
questions_list.append(question_data)
|
||||
|
||||
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
|
||||
|
||||
safe_title = "".join(c if c.isalnum() or c in " -_" else "_" for c in test.title)[:50]
|
||||
filename = f"{safe_title}.json"
|
||||
|
||||
assert _callback.message is not None
|
||||
await _callback.message.answer_document(
|
||||
document=BufferedInputFile(json_str.encode("utf-8"), filename=filename),
|
||||
caption=f"📤 <b>Экспорт теста</b>\n\n📝 {test.title}",
|
||||
)
|
||||
|
||||
|
||||
templates_dialog = Dialog(
|
||||
Window(
|
||||
Const(TEMPLATES_INFO),
|
||||
Row(
|
||||
Button(Const("📤 Экспорт"), id="export", on_click=on_export_clicked),
|
||||
Button(Const("📥 Импорт"), id="import", on_click=on_import_clicked),
|
||||
),
|
||||
Button(Const("📋 Спецификация"), id="spec", on_click=on_spec_clicked),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_clicked),
|
||||
state=AdminTemplatesSG.main,
|
||||
),
|
||||
Window(
|
||||
Format("<b>📤 Экспорт теста</b>\n\nВыберите тест для экспорта:\n\nВсего: {count}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="test_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="tests",
|
||||
on_click=on_test_selected_for_export,
|
||||
),
|
||||
id="tests_scroll",
|
||||
width=1,
|
||||
height=7,
|
||||
),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_to_templates),
|
||||
state=AdminTemplatesSG.export_list,
|
||||
getter=get_tests_for_export,
|
||||
),
|
||||
)
|
||||
@@ -6,6 +6,7 @@ from aiogram_dialog.widgets.text import Const
|
||||
from trudex.application.bot.creator_dialogs.states import (CreatorBroadcastSG,
|
||||
CreatorGroupsSG,
|
||||
CreatorMenuSG,
|
||||
CreatorTemplatesSG,
|
||||
CreatorTestsSG,
|
||||
CreatorUsersSG)
|
||||
|
||||
@@ -26,6 +27,10 @@ async def on_broadcast_clicked(_callback: CallbackQuery, _button: Button, manage
|
||||
await manager.start(CreatorBroadcastSG.broadcast_input, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_templates_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.start(CreatorTemplatesSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
creator_menu_dialog = Dialog(
|
||||
Window(
|
||||
Const("👑 <b>Панель создателя</b>\n\nВыберите раздел:"),
|
||||
@@ -34,6 +39,7 @@ creator_menu_dialog = Dialog(
|
||||
Button(Const("👥 Пользователи"), id="users", on_click=on_users_clicked),
|
||||
Button(Const("🎓 Группы"), id="groups", on_click=on_groups_clicked),
|
||||
Button(Const("📢 Рассылка"), id="broadcast", on_click=on_broadcast_clicked),
|
||||
Button(Const("📦 Шаблоны тестов"), id="templates", on_click=on_templates_clicked),
|
||||
),
|
||||
state=CreatorMenuSG.main,
|
||||
),
|
||||
|
||||
@@ -5,6 +5,11 @@ class CreatorMenuSG(StatesGroup):
|
||||
main = State()
|
||||
|
||||
|
||||
class CreatorTemplatesSG(StatesGroup):
|
||||
main = State()
|
||||
export_list = State()
|
||||
|
||||
|
||||
class CreatorUsersSG(StatesGroup):
|
||||
users_list = State()
|
||||
users_input = State()
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
import json
|
||||
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery
|
||||
from aiogram_dialog import Dialog, DialogManager, StartMode, Window
|
||||
from aiogram_dialog.widgets.kbd import Button, 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 CreatorMenuSG, CreatorTemplatesSG
|
||||
from trudex.infrastructure.database.dao.test import TestDAO
|
||||
from trudex.infrastructure.database.repo.test import TestRepository
|
||||
|
||||
|
||||
TEMPLATES_INFO = (
|
||||
"<b>📦 Шаблоны тестов</b>\n\n"
|
||||
"Шаблоны позволяют экспортировать и импортировать тесты в формате JSON.\n\n"
|
||||
"🔹 <b>Экспорт</b> — сохраните тест как файл для резервной копии или передачи\n"
|
||||
"🔹 <b>Импорт</b> — загрузите тест из файла\n"
|
||||
"🔹 <b>Спецификация</b> — описание формата JSON для создания тестов вручную"
|
||||
)
|
||||
|
||||
|
||||
async def on_export_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(CreatorTemplatesSG.export_list)
|
||||
|
||||
|
||||
async def on_import_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||
await _callback.answer("🚧 В разработке", show_alert=True)
|
||||
|
||||
|
||||
async def on_spec_clicked(_callback: CallbackQuery, _button: Button, _manager: DialogManager) -> None:
|
||||
await _callback.answer("🚧 В разработке", show_alert=True)
|
||||
|
||||
|
||||
async def on_back_clicked(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.start(CreatorMenuSG.main, mode=StartMode.RESET_STACK)
|
||||
|
||||
|
||||
async def on_back_to_templates(_callback: CallbackQuery, _button: Button, manager: DialogManager) -> None:
|
||||
await manager.switch_to(CreatorTemplatesSG.main)
|
||||
|
||||
|
||||
@inject
|
||||
async def get_tests_for_export(test_dao: FromDishka[TestDAO], **_kwargs):
|
||||
tests = await test_dao.get_all()
|
||||
return {
|
||||
"tests": [(f"📝 {t.title}", t.id) for t in tests],
|
||||
"count": len(tests),
|
||||
}
|
||||
|
||||
|
||||
@inject
|
||||
async def on_test_selected_for_export(
|
||||
_callback: CallbackQuery,
|
||||
_widget: Select,
|
||||
_manager: DialogManager,
|
||||
item_id: str,
|
||||
test_repo: FromDishka[TestRepository],
|
||||
):
|
||||
test_id = int(item_id)
|
||||
test, questions_with_options = await test_repo.get_full_test(test_id)
|
||||
|
||||
if not test:
|
||||
await _callback.answer("❌ Тест не найден")
|
||||
return
|
||||
|
||||
export_data = {
|
||||
"title": test.title,
|
||||
"description": test.description,
|
||||
"password": test.password,
|
||||
"attempts": test.attempts,
|
||||
"for_group": test.for_group,
|
||||
"questions": [],
|
||||
}
|
||||
|
||||
for question, options in questions_with_options:
|
||||
question_data = {
|
||||
"text": question.text,
|
||||
"question_type": question.question_type,
|
||||
}
|
||||
|
||||
if question.question_type == "input":
|
||||
correct_options = [o for o in options if o.is_correct]
|
||||
if correct_options:
|
||||
question_data["correct_answer"] = correct_options[0].text
|
||||
else:
|
||||
question_data["options"] = [
|
||||
{"text": o.text, "is_correct": o.is_correct}
|
||||
for o in options
|
||||
]
|
||||
|
||||
export_data["questions"].append(question_data)
|
||||
|
||||
json_str = json.dumps(export_data, ensure_ascii=False, indent=2)
|
||||
|
||||
safe_title = "".join(c if c.isalnum() or c in " -_" else "_" for c in test.title)[:50]
|
||||
filename = f"{safe_title}.json"
|
||||
|
||||
assert _callback.message is not None
|
||||
await _callback.message.answer_document(
|
||||
document=BufferedInputFile(json_str.encode("utf-8"), filename=filename),
|
||||
caption=f"📤 <b>Экспорт теста</b>\n\n📝 {test.title}",
|
||||
)
|
||||
|
||||
|
||||
templates_dialog = Dialog(
|
||||
Window(
|
||||
Const(TEMPLATES_INFO),
|
||||
Row(
|
||||
Button(Const("📤 Экспорт"), id="export", on_click=on_export_clicked),
|
||||
Button(Const("📥 Импорт"), id="import", on_click=on_import_clicked),
|
||||
),
|
||||
Button(Const("📋 Спецификация"), id="spec", on_click=on_spec_clicked),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_clicked),
|
||||
state=CreatorTemplatesSG.main,
|
||||
),
|
||||
Window(
|
||||
Format("<b>📤 Экспорт теста</b>\n\nВыберите тест для экспорта:\n\nВсего: {count}"),
|
||||
ScrollingGroup(
|
||||
Select(
|
||||
Format("{item[0]}"),
|
||||
id="test_select",
|
||||
item_id_getter=lambda x: x[1],
|
||||
items="tests",
|
||||
on_click=on_test_selected_for_export,
|
||||
),
|
||||
id="tests_scroll",
|
||||
width=1,
|
||||
height=7,
|
||||
),
|
||||
Button(Const("◀️ Назад"), id="back", on_click=on_back_to_templates),
|
||||
state=CreatorTemplatesSG.export_list,
|
||||
getter=get_tests_for_export,
|
||||
),
|
||||
)
|
||||
Reference in New Issue
Block a user