From 43efcd481a41d7e0da0d658159e09ec7a82a5e50 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 01:19:34 +0300 Subject: [PATCH 01/12] handler --- bot/handlers/marking_students | 140 ++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 bot/handlers/marking_students diff --git a/bot/handlers/marking_students b/bot/handlers/marking_students new file mode 100644 index 0000000..6ef6157 --- /dev/null +++ b/bot/handlers/marking_students @@ -0,0 +1,140 @@ +from aiogram import F, Router +from aiogram.types import CallbackQuery, Message +from aiogram.filters import Command +from aiogram.utils.keyboard import InlineKeyboardBuilder +from database.user_dao import UserDAO +from database.db import get_db +from aiogram.types import ReplyKeyboardRemove, \ + ReplyKeyboardMarkup, KeyboardButton, \ + InlineKeyboardMarkup, InlineKeyboardButton + +router = Router() + +with get_db() as db: + UserDao = UserDAO(db) + + EVENTS = None #список всех событий + + + STUDENTS = UserDao.get_all_students() + +# Кэш для хранения выбранных студентов +user_selections = {} + +@router.message(Command("mark_students")) +async def show_events(message: Message): + builder = InlineKeyboardBuilder() + for event in EVENTS: + builder.button( + text=f"{event['name']} ({event['date']})", + callback_data=f"event_{event['id']}" + ) + builder.adjust(1) + await message.answer( + "Выберите мероприятие:", + reply_markup=builder.as_markup() + ) + +@router.callback_query(F.data.startswith("event_")) +async def select_students(callback: CallbackQuery): + event_id = int(callback.data.split("_")[1]) + user_selections[callback.from_user.id] = {"event_id": event_id, "selected": set()} + + # Отправляем первую страницу студентов + await show_students_page(callback, page=0) + +async def show_students_page(callback: CallbackQuery, page: int): + event_id = user_selections[callback.from_user.id]["event_id"] + selected = user_selections[callback.from_user.id]["selected"] + + # Разбиваем студентов на страницы по 5 человек + students_per_page = 5 + start_idx = page * students_per_page + page_students = STUDENTS[start_idx:start_idx + students_per_page] + + builder = InlineKeyboardBuilder() + for student in page_students: + status = "✅" if student["id"] in selected else "◻️" + builder.button( + text=f"{status} {student['name']} ({student['username']})", + callback_data=f"toggle_{student['id']}_{page}" + ) + + # Кнопки навигации + nav_buttons = [] + if page > 0: + nav_buttons.append(KeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}")) + if (page + 1) * students_per_page < len(STUDENTS): + nav_buttons.append(KeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}")) + nav_buttons.append(KeyboardButton(text="⏭️ Подтвердить", callback_data="confirm")) + + builder.row(*nav_buttons) + + await callback.message.edit_text( + f"Выберите студентов для мероприятия {EVENTS[event_id-1]['name']} [Страница {page+1} из {len(STUDENTS)//students_per_page + 1}]", + reply_markup=builder.as_markup() + ) + +@router.callback_query(F.data.startswith("toggle_")) +async def toggle_student(callback: CallbackQuery): + _, student_id, page = callback.data.split("_") + student_id = int(student_id) + page = int(page) + + selected = user_selections[callback.from_user.id]["selected"] + if student_id in selected: + selected.remove(student_id) + else: + selected.add(student_id) + + await show_students_page(callback, page) + +@router.callback_query(F.data.startswith("page_")) +async def change_page(callback: CallbackQuery): + page = int(callback.data.split("_")[1]) + await show_students_page(callback, page) + +@router.callback_query(F.data == "confirm") +async def confirm_selection(callback: CallbackQuery): + user_data = user_selections[callback.from_user.id] + selected_ids = user_data["selected"] + event_id = user_data["event_id"] + + selected_students = [ + f"{s['name']} ({s['username']})" + for s in STUDENTS + if s["id"] in selected_ids + ] + + if not selected_students: + await callback.answer("Не выбрано ни одного студента!") + return + + builder = InlineKeyboardBuilder() + builder.button(text="✅ Подтвердить посещения", callback_data="final_confirm") + builder.button(text="❌ Отменить", callback_data="cancel") + + await callback.message.edit_text( + f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{EVENTS[event_id-1]['name']}\":\n\n" + + "\n".join(selected_students), + reply_markup=builder.as_markup() + ) + +@router.callback_query(F.data == "final_confirm") +async def final_confirmation(callback: CallbackQuery): + user_data = user_selections[callback.from_user.id] + event_name = EVENTS[user_data["event_id"]-1]["name"] + + # Здесь должна быть логика сохранения в БД + # ... + + await callback.message.edit_text( + f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{event_name}\"." + ) + del user_selections[callback.from_user.id] + +@router.callback_query(F.data == "cancel") +async def cancel_action(callback: CallbackQuery): + if callback.from_user.id in user_selections: + del user_selections[callback.from_user.id] + await callback.message.edit_text("Действие отменено.") \ No newline at end of file From c43f7be01feb62a6508a428c10c53149c6b28b40 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 01:24:04 +0300 Subject: [PATCH 02/12] View all events method --- bot/database/competition_dao.py | 5 +++++ bot/handlers/marking_students | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bot/database/competition_dao.py b/bot/database/competition_dao.py index 154fd3d..570b211 100644 --- a/bot/database/competition_dao.py +++ b/bot/database/competition_dao.py @@ -29,3 +29,8 @@ def add_competition( self.session.commit() self.session.refresh(new_competition) return new_competition + + def get_all_competition( self ): + return ( + self.session.query(Competition).all() + ) diff --git a/bot/handlers/marking_students b/bot/handlers/marking_students index 6ef6157..7a4dccc 100644 --- a/bot/handlers/marking_students +++ b/bot/handlers/marking_students @@ -3,6 +3,7 @@ from aiogram.types import CallbackQuery, Message from aiogram.filters import Command from aiogram.utils.keyboard import InlineKeyboardBuilder from database.user_dao import UserDAO +from database.competition_dao import CompetitionDao from database.db import get_db from aiogram.types import ReplyKeyboardRemove, \ ReplyKeyboardMarkup, KeyboardButton, \ @@ -12,8 +13,12 @@ router = Router() with get_db() as db: UserDao = UserDAO(db) + CompetitionDAO = CompetitionDao(db) - EVENTS = None #список всех событий + + EVENTS = CompetitionDAO.get_all_competition() + + #список всех событий STUDENTS = UserDao.get_all_students() From 96d3e61a98dba371618155588ce26c04c4424667 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 01:44:48 +0300 Subject: [PATCH 03/12] =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B0=D1=80=D0=B8=D0=B9=20=D1=82=D1=83=D0=B4=D0=B0=D1=81=D1=8E?= =?UTF-8?q?=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/handlers/marking_students | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bot/handlers/marking_students b/bot/handlers/marking_students index 7a4dccc..d51e6b0 100644 --- a/bot/handlers/marking_students +++ b/bot/handlers/marking_students @@ -16,9 +16,7 @@ with get_db() as db: CompetitionDAO = CompetitionDao(db) - EVENTS = CompetitionDAO.get_all_competition() - - #список всех событий + EVENTS = CompetitionDAO.get_all_competition()#список всех событий STUDENTS = UserDao.get_all_students() From a74718bd41aa0c76942348e2ac5809072605d002 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 02:01:06 +0300 Subject: [PATCH 04/12] Working --- bot/handlers/__init__.py | 1 + .../{marking_students => marking_students.py} | 85 +++++++++++-------- bot/main.py | 2 + 3 files changed, 51 insertions(+), 37 deletions(-) rename bot/handlers/{marking_students => marking_students.py} (57%) diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py index 1a7b678..44c88cc 100644 --- a/bot/handlers/__init__.py +++ b/bot/handlers/__init__.py @@ -6,3 +6,4 @@ from .heal import heal_router from .leaderboard import leaderboard_router from .profiles import my_profile_router +from .marking_students import mark_students_router \ No newline at end of file diff --git a/bot/handlers/marking_students b/bot/handlers/marking_students.py similarity index 57% rename from bot/handlers/marking_students rename to bot/handlers/marking_students.py index d51e6b0..5c23f6e 100644 --- a/bot/handlers/marking_students +++ b/bot/handlers/marking_students.py @@ -1,36 +1,30 @@ from aiogram import F, Router -from aiogram.types import CallbackQuery, Message +from aiogram.types import CallbackQuery, Message, InlineKeyboardButton from aiogram.filters import Command from aiogram.utils.keyboard import InlineKeyboardBuilder from database.user_dao import UserDAO from database.competition_dao import CompetitionDao from database.db import get_db -from aiogram.types import ReplyKeyboardRemove, \ - ReplyKeyboardMarkup, KeyboardButton, \ - InlineKeyboardMarkup, InlineKeyboardButton -router = Router() +mark_students_router = Router() +# Инициализация DAO with get_db() as db: UserDao = UserDAO(db) CompetitionDAO = CompetitionDao(db) - - - EVENTS = CompetitionDAO.get_all_competition()#список всех событий - - - STUDENTS = UserDao.get_all_students() + EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition + STUDENTS = UserDao.get_all_students() # список объектов Student # Кэш для хранения выбранных студентов user_selections = {} -@router.message(Command("mark_students")) +@mark_students_router.message(Command("mark_students")) async def show_events(message: Message): builder = InlineKeyboardBuilder() for event in EVENTS: builder.button( - text=f"{event['name']} ({event['date']})", - callback_data=f"event_{event['id']}" + text=f"{event.name} ({event.date.strftime('%d.%m.%Y')})", + callback_data=f"event_{event.id}" ) builder.adjust(1) await message.answer( @@ -38,47 +32,61 @@ async def show_events(message: Message): reply_markup=builder.as_markup() ) -@router.callback_query(F.data.startswith("event_")) +@mark_students_router.callback_query(F.data.startswith("event_")) async def select_students(callback: CallbackQuery): event_id = int(callback.data.split("_")[1]) - user_selections[callback.from_user.id] = {"event_id": event_id, "selected": set()} + # Находим мероприятие по ID + competition = next((e for e in EVENTS if e.id == event_id), None) + + if not competition: + await callback.answer("Мероприятие не найдено!") + return + + user_selections[callback.from_user.id] = { + "event_id": event_id, + "selected": set(), + "competition_name": competition.name + } # Отправляем первую страницу студентов await show_students_page(callback, page=0) async def show_students_page(callback: CallbackQuery, page: int): - event_id = user_selections[callback.from_user.id]["event_id"] - selected = user_selections[callback.from_user.id]["selected"] + user_data = user_selections.get(callback.from_user.id) + if not user_data: + await callback.answer("Сессия устарела, начните заново!") + return # Разбиваем студентов на страницы по 5 человек students_per_page = 5 start_idx = page * students_per_page page_students = STUDENTS[start_idx:start_idx + students_per_page] + total_pages = (len(STUDENTS) + students_per_page - 1) // students_per_page builder = InlineKeyboardBuilder() for student in page_students: - status = "✅" if student["id"] in selected else "◻️" + status = "✅" if student.id in user_data["selected"] else "◻️" builder.button( - text=f"{status} {student['name']} ({student['username']})", - callback_data=f"toggle_{student['id']}_{page}" + text=f"{status} {student.full_name} ({student.username})", + callback_data=f"toggle_{student.id}_{page}" ) # Кнопки навигации nav_buttons = [] if page > 0: - nav_buttons.append(KeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}")) + nav_buttons.append(InlineKeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}")) if (page + 1) * students_per_page < len(STUDENTS): - nav_buttons.append(KeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}")) - nav_buttons.append(KeyboardButton(text="⏭️ Подтвердить", callback_data="confirm")) + nav_buttons.append(InlineKeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}")) + nav_buttons.append(InlineKeyboardButton(text="⏭️ Подтвердить", callback_data="confirm")) builder.row(*nav_buttons) await callback.message.edit_text( - f"Выберите студентов для мероприятия {EVENTS[event_id-1]['name']} [Страница {page+1} из {len(STUDENTS)//students_per_page + 1}]", + f"Выберите студентов для мероприятия {user_data['competition_name']} [Страница {page+1} из {total_pages}]", reply_markup=builder.as_markup() ) -@router.callback_query(F.data.startswith("toggle_")) +@mark_students_router.callback_query(F.data.startswith("toggle_")) async def toggle_student(callback: CallbackQuery): _, student_id, page = callback.data.split("_") student_id = int(student_id) @@ -92,21 +100,20 @@ async def toggle_student(callback: CallbackQuery): await show_students_page(callback, page) -@router.callback_query(F.data.startswith("page_")) +@mark_students_router.callback_query(F.data.startswith("page_")) async def change_page(callback: CallbackQuery): page = int(callback.data.split("_")[1]) await show_students_page(callback, page) -@router.callback_query(F.data == "confirm") +@mark_students_router.callback_query(F.data == "confirm") async def confirm_selection(callback: CallbackQuery): user_data = user_selections[callback.from_user.id] selected_ids = user_data["selected"] - event_id = user_data["event_id"] selected_students = [ - f"{s['name']} ({s['username']})" + f"{s.full_name} ({s.username})" for s in STUDENTS - if s["id"] in selected_ids + if s.id in selected_ids ] if not selected_students: @@ -118,25 +125,29 @@ async def confirm_selection(callback: CallbackQuery): builder.button(text="❌ Отменить", callback_data="cancel") await callback.message.edit_text( - f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{EVENTS[event_id-1]['name']}\":\n\n" + f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{user_data['competition_name']}\":\n\n" + "\n".join(selected_students), reply_markup=builder.as_markup() ) -@router.callback_query(F.data == "final_confirm") +@mark_students_router.callback_query(F.data == "final_confirm") async def final_confirmation(callback: CallbackQuery): user_data = user_selections[callback.from_user.id] - event_name = EVENTS[user_data["event_id"]-1]["name"] # Здесь должна быть логика сохранения в БД - # ... + # Например: + # with get_db() as db: + # ParticipationDAO(db).mark_participation( + # user_data['event_id'], + # list(user_data['selected']) + # ) await callback.message.edit_text( - f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{event_name}\"." + f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{user_data['competition_name']}\"." ) del user_selections[callback.from_user.id] -@router.callback_query(F.data == "cancel") +@mark_students_router.callback_query(F.data == "cancel") async def cancel_action(callback: CallbackQuery): if callback.from_user.id in user_selections: del user_selections[callback.from_user.id] diff --git a/bot/main.py b/bot/main.py index 440f4be..d9f3f34 100644 --- a/bot/main.py +++ b/bot/main.py @@ -18,6 +18,7 @@ heal_router, leaderboard_router, my_profile_router, + mark_students_router ) from settings import config from middlewares import AuthMiddleware @@ -45,6 +46,7 @@ dp.include_routers( start_router, + mark_students_router, add_task_router, add_competition_router, my_tasks_router, From 9194d8679af04fdcd09148ce10e9288054b1568d Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 02:04:02 +0300 Subject: [PATCH 05/12] Flake8 --- bot/database/competition_dao.py | 8 ++-- bot/database/user_dao.py | 1 - bot/handlers/__init__.py | 2 +- bot/handlers/marking_students.py | 79 ++++++++++++++++++-------------- bot/main.py | 3 +- bot/tasks.py | 10 +++- 6 files changed, 58 insertions(+), 45 deletions(-) diff --git a/bot/database/competition_dao.py b/bot/database/competition_dao.py index 570b211..cd3999e 100644 --- a/bot/database/competition_dao.py +++ b/bot/database/competition_dao.py @@ -29,8 +29,6 @@ def add_competition( self.session.commit() self.session.refresh(new_competition) return new_competition - - def get_all_competition( self ): - return ( - self.session.query(Competition).all() - ) + + def get_all_competition(self): + return self.session.query(Competition).all() diff --git a/bot/database/user_dao.py b/bot/database/user_dao.py index 3ee6dac..fa90566 100644 --- a/bot/database/user_dao.py +++ b/bot/database/user_dao.py @@ -38,7 +38,6 @@ def get_all_students(self) -> list[User]: def get_all_active_students(self): return ( self.session.query(User) - .filter(User.username.isnot(None), User.lives > 0) .all() ) diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py index 44c88cc..b3f4862 100644 --- a/bot/handlers/__init__.py +++ b/bot/handlers/__init__.py @@ -6,4 +6,4 @@ from .heal import heal_router from .leaderboard import leaderboard_router from .profiles import my_profile_router -from .marking_students import mark_students_router \ No newline at end of file +from .marking_students import mark_students_router diff --git a/bot/handlers/marking_students.py b/bot/handlers/marking_students.py index 5c23f6e..db96f77 100644 --- a/bot/handlers/marking_students.py +++ b/bot/handlers/marking_students.py @@ -13,127 +13,135 @@ UserDao = UserDAO(db) CompetitionDAO = CompetitionDao(db) EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition - STUDENTS = UserDao.get_all_students() # список объектов Student + STUDENTS = UserDao.get_all_students() # список объектов Student # Кэш для хранения выбранных студентов user_selections = {} + @mark_students_router.message(Command("mark_students")) async def show_events(message: Message): builder = InlineKeyboardBuilder() for event in EVENTS: builder.button( - text=f"{event.name} ({event.date.strftime('%d.%m.%Y')})", - callback_data=f"event_{event.id}" + text=f"{event.name} ({event.date.strftime('%d.%m.%Y')})", + callback_data=f"event_{event.id}", ) builder.adjust(1) - await message.answer( - "Выберите мероприятие:", - reply_markup=builder.as_markup() - ) + await message.answer("Выберите мероприятие:", reply_markup=builder.as_markup()) + @mark_students_router.callback_query(F.data.startswith("event_")) async def select_students(callback: CallbackQuery): event_id = int(callback.data.split("_")[1]) # Находим мероприятие по ID competition = next((e for e in EVENTS if e.id == event_id), None) - + if not competition: await callback.answer("Мероприятие не найдено!") return - + user_selections[callback.from_user.id] = { "event_id": event_id, "selected": set(), - "competition_name": competition.name + "competition_name": competition.name, } - + # Отправляем первую страницу студентов await show_students_page(callback, page=0) + async def show_students_page(callback: CallbackQuery, page: int): user_data = user_selections.get(callback.from_user.id) if not user_data: await callback.answer("Сессия устарела, начните заново!") return - + # Разбиваем студентов на страницы по 5 человек students_per_page = 5 start_idx = page * students_per_page - page_students = STUDENTS[start_idx:start_idx + students_per_page] + page_students = STUDENTS[start_idx : start_idx + students_per_page] total_pages = (len(STUDENTS) + students_per_page - 1) // students_per_page - + builder = InlineKeyboardBuilder() for student in page_students: status = "✅" if student.id in user_data["selected"] else "◻️" builder.button( text=f"{status} {student.full_name} ({student.username})", - callback_data=f"toggle_{student.id}_{page}" + callback_data=f"toggle_{student.id}_{page}", ) - + # Кнопки навигации nav_buttons = [] if page > 0: - nav_buttons.append(InlineKeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}")) + nav_buttons.append( + InlineKeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}") + ) if (page + 1) * students_per_page < len(STUDENTS): - nav_buttons.append(InlineKeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}")) - nav_buttons.append(InlineKeyboardButton(text="⏭️ Подтвердить", callback_data="confirm")) - + nav_buttons.append( + InlineKeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}") + ) + nav_buttons.append( + InlineKeyboardButton(text="⏭️ Подтвердить", callback_data="confirm") + ) + builder.row(*nav_buttons) - + await callback.message.edit_text( f"Выберите студентов для мероприятия {user_data['competition_name']} [Страница {page+1} из {total_pages}]", - reply_markup=builder.as_markup() + reply_markup=builder.as_markup(), ) + @mark_students_router.callback_query(F.data.startswith("toggle_")) async def toggle_student(callback: CallbackQuery): _, student_id, page = callback.data.split("_") student_id = int(student_id) page = int(page) - + selected = user_selections[callback.from_user.id]["selected"] if student_id in selected: selected.remove(student_id) else: selected.add(student_id) - + await show_students_page(callback, page) + @mark_students_router.callback_query(F.data.startswith("page_")) async def change_page(callback: CallbackQuery): page = int(callback.data.split("_")[1]) await show_students_page(callback, page) + @mark_students_router.callback_query(F.data == "confirm") async def confirm_selection(callback: CallbackQuery): user_data = user_selections[callback.from_user.id] selected_ids = user_data["selected"] - + selected_students = [ - f"{s.full_name} ({s.username})" - for s in STUDENTS - if s.id in selected_ids + f"{s.full_name} ({s.username})" for s in STUDENTS if s.id in selected_ids ] - + if not selected_students: await callback.answer("Не выбрано ни одного студента!") return - + builder = InlineKeyboardBuilder() builder.button(text="✅ Подтвердить посещения", callback_data="final_confirm") builder.button(text="❌ Отменить", callback_data="cancel") - + await callback.message.edit_text( f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{user_data['competition_name']}\":\n\n" + "\n".join(selected_students), - reply_markup=builder.as_markup() + reply_markup=builder.as_markup(), ) + @mark_students_router.callback_query(F.data == "final_confirm") async def final_confirmation(callback: CallbackQuery): user_data = user_selections[callback.from_user.id] - + # Здесь должна быть логика сохранения в БД # Например: # with get_db() as db: @@ -141,14 +149,15 @@ async def final_confirmation(callback: CallbackQuery): # user_data['event_id'], # list(user_data['selected']) # ) - + await callback.message.edit_text( f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{user_data['competition_name']}\"." ) del user_selections[callback.from_user.id] + @mark_students_router.callback_query(F.data == "cancel") async def cancel_action(callback: CallbackQuery): if callback.from_user.id in user_selections: del user_selections[callback.from_user.id] - await callback.message.edit_text("Действие отменено.") \ No newline at end of file + await callback.message.edit_text("Действие отменено.") diff --git a/bot/main.py b/bot/main.py index d9f3f34..95ea499 100644 --- a/bot/main.py +++ b/bot/main.py @@ -18,7 +18,7 @@ heal_router, leaderboard_router, my_profile_router, - mark_students_router + mark_students_router, ) from settings import config from middlewares import AuthMiddleware @@ -93,5 +93,6 @@ async def main(): await dp.start_polling(bot) + if __name__ == "__main__": asyncio.run(main()) diff --git a/bot/tasks.py b/bot/tasks.py index 580d0d6..d3019e2 100644 --- a/bot/tasks.py +++ b/bot/tasks.py @@ -26,10 +26,16 @@ async def sync_education_tasks(bot: Bot): ) for task in user.tasks: task.completed = task.name in solved_tasks - if not task.completed and task.is_expired and not task.violation_recorded: + if ( + not task.completed + and task.is_expired + and not task.violation_recorded + ): user.lives -= 1 user.violations += 1 - task.violation_recorded = True # Отмечаем, что нарушение обработано + task.violation_recorded = ( + True # Отмечаем, что нарушение обработано + ) teacher_message = ( f"Задача {task.name} истека у студента {user}." ) From d83b459e9c69cb35627915a6409cdc80d84bb908 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 13:16:29 +0300 Subject: [PATCH 06/12] ParticipationDAO --- bot/database/Participationdao.py | 70 ++++++++++++++++++++++++++++++++ bot/handlers/marking_students.py | 24 ++++++----- 2 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 bot/database/Participationdao.py diff --git a/bot/database/Participationdao.py b/bot/database/Participationdao.py new file mode 100644 index 0000000..91674eb --- /dev/null +++ b/bot/database/Participationdao.py @@ -0,0 +1,70 @@ +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, and_ +from datetime import datetime +from database.models import Participation +from database.models import User +from database.models import Competition + + +class ParticipationDAO: + def __init__(self, session: AsyncSession): + self.session = session + + async def mark_participation(self, competition_id: int, student_ids: list[int]): + """ + Отмечает участие студентов в мероприятии + :param competition_id: ID мероприятия + :param student_ids: Список ID студентов + """ + try: + # Проверяем существование мероприятия + competition = await self.session.get(Competition, competition_id) + if not competition: + raise ValueError(f"Мероприятие с ID {competition_id} не найдено") + + # Для каждого студента создаем или обновляем запись об участии + for student_id in student_ids: + # Проверяем существование студента + student = await self.session.get(User, student_id) + if not student: + continue # или можно вызвать исключение + + # Ищем существующую запись об участии + existing_participation = await self.session.execute( + select(Participation).where( + and_( + Participation.user_id == student_id, + Participation.competition_id == competition_id, + ) + ) + ) + existing_participation = existing_participation.scalar_one_or_none() + + if existing_participation: + # Если запись уже существует, можно обновить (например, дату) + existing_participation.participation_date = datetime.utcnow() + else: + # Создаем новую запись об участии + new_participation = Participation( + user_id=student_id, + competition_id=competition_id, + points_awarded=competition.points, # если начисляются баллы + ) + self.session.add(new_participation) + + await self.session.commit() + return True + except Exception as e: + await self.session.rollback() + raise e + + async def get_participants(self, competition_id: int): + """ + Получает список участников мероприятия + :param competition_id: ID мероприятия + :return: Список объектов Participation + """ + result = await self.session.execute( + select(Participation).where(Participation.competition_id == competition_id) + ) + return result.scalars().all() diff --git a/bot/handlers/marking_students.py b/bot/handlers/marking_students.py index db96f77..f4a4706 100644 --- a/bot/handlers/marking_students.py +++ b/bot/handlers/marking_students.py @@ -4,6 +4,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder from database.user_dao import UserDAO from database.competition_dao import CompetitionDao +from database.Participationdao import ParticipationDAO from database.db import get_db mark_students_router = Router() @@ -142,18 +143,19 @@ async def confirm_selection(callback: CallbackQuery): async def final_confirmation(callback: CallbackQuery): user_data = user_selections[callback.from_user.id] - # Здесь должна быть логика сохранения в БД - # Например: - # with get_db() as db: - # ParticipationDAO(db).mark_participation( - # user_data['event_id'], - # list(user_data['selected']) - # ) + try: + with get_db() as db: + participation_dao = ParticipationDAO(db) + await participation_dao.mark_participation( + user_data["event_id"], list(user_data["selected"]) + ) - await callback.message.edit_text( - f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{user_data['competition_name']}\"." - ) - del user_selections[callback.from_user.id] + await callback.message.edit_text( + f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{user_data['competition_name']}\"." + ) + del user_selections[callback.from_user.id] + except Exception as e: + await callback.answer(f"Ошибка: {str(e)}") @mark_students_router.callback_query(F.data == "cancel") From 483b6f1696858f5a6d4caa368851109d32b3719d Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 14:03:09 +0300 Subject: [PATCH 07/12] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/database/Participationdao.py | 59 ++++++-------------------------- bot/handlers/marking_students.py | 2 +- 2 files changed, 12 insertions(+), 49 deletions(-) diff --git a/bot/database/Participationdao.py b/bot/database/Participationdao.py index 91674eb..d94f606 100644 --- a/bot/database/Participationdao.py +++ b/bot/database/Participationdao.py @@ -7,64 +7,27 @@ class ParticipationDAO: - def __init__(self, session: AsyncSession): + def __init__(self, session): self.session = session - async def mark_participation(self, competition_id: int, student_ids: list[int]): - """ - Отмечает участие студентов в мероприятии - :param competition_id: ID мероприятия - :param student_ids: Список ID студентов - """ + def mark_participation(self, competition_id: int, student_ids: list[int]): + """Синхронный метод для отметки участия""" try: - # Проверяем существование мероприятия - competition = await self.session.get(Competition, competition_id) - if not competition: - raise ValueError(f"Мероприятие с ID {competition_id} не найдено") - - # Для каждого студента создаем или обновляем запись об участии for student_id in student_ids: - # Проверяем существование студента - student = await self.session.get(User, student_id) - if not student: - continue # или можно вызвать исключение - - # Ищем существующую запись об участии - existing_participation = await self.session.execute( - select(Participation).where( - and_( - Participation.user_id == student_id, - Participation.competition_id == competition_id, - ) - ) + existing = ( + self.session.query(Participation) + .filter_by(user_id=student_id, competition_id=competition_id) + .first() ) - existing_participation = existing_participation.scalar_one_or_none() - if existing_participation: - # Если запись уже существует, можно обновить (например, дату) - existing_participation.participation_date = datetime.utcnow() - else: - # Создаем новую запись об участии + if not existing: new_participation = Participation( - user_id=student_id, - competition_id=competition_id, - points_awarded=competition.points, # если начисляются баллы + user_id=student_id, competition_id=competition_id ) self.session.add(new_participation) - await self.session.commit() + self.session.commit() return True except Exception as e: - await self.session.rollback() + self.session.rollback() raise e - - async def get_participants(self, competition_id: int): - """ - Получает список участников мероприятия - :param competition_id: ID мероприятия - :return: Список объектов Participation - """ - result = await self.session.execute( - select(Participation).where(Participation.competition_id == competition_id) - ) - return result.scalars().all() diff --git a/bot/handlers/marking_students.py b/bot/handlers/marking_students.py index f4a4706..8b2313a 100644 --- a/bot/handlers/marking_students.py +++ b/bot/handlers/marking_students.py @@ -146,7 +146,7 @@ async def final_confirmation(callback: CallbackQuery): try: with get_db() as db: participation_dao = ParticipationDAO(db) - await participation_dao.mark_participation( + participation_dao.mark_participation( user_data["event_id"], list(user_data["selected"]) ) From cd1be277a981c1954d39c091eddb1724035ebbe2 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 14:03:52 +0300 Subject: [PATCH 08/12] blacking --- bot/database/user_dao.py | 27 +++++---------------------- bot/handlers/leaderboard.py | 8 ++++---- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/bot/database/user_dao.py b/bot/database/user_dao.py index 85991fe..ef791f6 100644 --- a/bot/database/user_dao.py +++ b/bot/database/user_dao.py @@ -32,9 +32,7 @@ def create_user( def get_all_students(self) -> List[User]: """Получить всех студентов (исключая учителей по tg_id).""" return ( - self.session.query(User) - .filter(User.tg_id.notin_(config.teacher_ids)) - .all() + self.session.query(User).filter(User.tg_id.notin_(config.teacher_ids)).all() ) def get_all_active_students(self) -> List[User]: @@ -47,20 +45,12 @@ def get_all_active_students(self) -> List[User]: def get_user_id_by_username(self, username: str) -> Optional[int]: """Вернуть внутренний ID пользователя по username или None.""" - user = ( - self.session.query(User) - .filter(User.username == username) - .first() - ) + user = self.session.query(User).filter(User.username == username).first() return user.id if user else None def get_user_by_tg_id(self, tg_id: int) -> Optional[User]: """Получить пользователя по его Telegram ID.""" - return ( - self.session.query(User) - .filter(User.tg_id == tg_id) - .first() - ) + return self.session.query(User).filter(User.tg_id == tg_id).first() def get_all_students_with_tasks(self) -> List[User]: """Получить всех студентов вместе с их невыполненными заданиями.""" @@ -85,18 +75,11 @@ def heal(self, user: User) -> None: def get_teachers(self) -> List[User]: """Получить всех учителей (старшекурсников) по tg_id.""" teachers = ( - self.session.query(User) - .filter(User.tg_id.in_(config.teacher_ids)) - .all() + self.session.query(User).filter(User.tg_id.in_(config.teacher_ids)).all() ) logger.info(f"Получены учителя: {teachers}") return teachers def leaderboard(self, limit: int = 20) -> List[User]: """Извлечь топ-`limit` пользователей, сортируя по убыванию points.""" - return ( - self.session.query(User) - .order_by(User.points.desc()) - .limit(limit) - .all() - ) + return self.session.query(User).order_by(User.points.desc()).limit(limit).all() diff --git a/bot/handlers/leaderboard.py b/bot/handlers/leaderboard.py index 5dc3a12..f2d945b 100644 --- a/bot/handlers/leaderboard.py +++ b/bot/handlers/leaderboard.py @@ -32,13 +32,13 @@ def format_user_status(user: User, top_rating: list[User]) -> list[str]: messages.append(f"🎉 Вы на {rank}-м месте в топ-20!") else: if user.points == 0: - messages.append("😔 У вас пока нет баллов. Решайте задачи, чтобы попасть в топ!") + messages.append( + "😔 У вас пока нет баллов. Решайте задачи, чтобы попасть в топ!" + ) else: last_top_score = top_rating[-1].points if top_rating else 0 needed = last_top_score - user.points + 1 - messages.append( - f"👉 Чтобы войти в топ-20, нужно ещё {needed} балл(ов)." - ) + messages.append(f"👉 Чтобы войти в топ-20, нужно ещё {needed} балл(ов).") return messages From 0a3d64e8ce9b18ec3e9c12449c0985625bc7ab15 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Fri, 30 May 2025 14:05:49 +0300 Subject: [PATCH 09/12] linter --- bot/database/Participationdao.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bot/database/Participationdao.py b/bot/database/Participationdao.py index d94f606..a7355e1 100644 --- a/bot/database/Participationdao.py +++ b/bot/database/Participationdao.py @@ -1,9 +1,4 @@ -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, and_ -from datetime import datetime from database.models import Participation -from database.models import User -from database.models import Competition class ParticipationDAO: From f836135af5f4ef2b69951696a4b4e7ad7be7d6e3 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Sun, 1 Jun 2025 20:12:59 +0300 Subject: [PATCH 10/12] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=82=D1=80=D0=B5=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/handlers/marking_students.py | 143 +++++++++++++++-------------- bot/main.py | 1 + bot/states/mark_students_states.py | 7 ++ bot/tasks.py | 2 +- 4 files changed, 82 insertions(+), 71 deletions(-) create mode 100644 bot/states/mark_students_states.py diff --git a/bot/handlers/marking_students.py b/bot/handlers/marking_students.py index 8b2313a..8742b41 100644 --- a/bot/handlers/marking_students.py +++ b/bot/handlers/marking_students.py @@ -1,11 +1,13 @@ from aiogram import F, Router from aiogram.types import CallbackQuery, Message, InlineKeyboardButton -from aiogram.filters import Command +from aiogram.filters import Command, StateFilter +from aiogram.fsm.context import FSMContext from aiogram.utils.keyboard import InlineKeyboardBuilder from database.user_dao import UserDAO from database.competition_dao import CompetitionDao from database.Participationdao import ParticipationDAO from database.db import get_db +from states.mark_students_states import MarkStudentsState mark_students_router = Router() @@ -13,74 +15,71 @@ with get_db() as db: UserDao = UserDAO(db) CompetitionDAO = CompetitionDao(db) - EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition - STUDENTS = UserDao.get_all_students() # список объектов Student - -# Кэш для хранения выбранных студентов -user_selections = {} - @mark_students_router.message(Command("mark_students")) -async def show_events(message: Message): +async def show_events(message: Message, state: FSMContext): builder = InlineKeyboardBuilder() + EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition for event in EVENTS: builder.button( text=f"{event.name} ({event.date.strftime('%d.%m.%Y')})", callback_data=f"event_{event.id}", ) builder.adjust(1) + + await state.set_state(MarkStudentsState.selecting_event) await message.answer("Выберите мероприятие:", reply_markup=builder.as_markup()) - -@mark_students_router.callback_query(F.data.startswith("event_")) -async def select_students(callback: CallbackQuery): +@mark_students_router.callback_query(F.data.startswith("event_"), StateFilter(MarkStudentsState.selecting_event)) +async def select_students(callback: CallbackQuery, state: FSMContext): event_id = int(callback.data.split("_")[1]) - # Находим мероприятие по ID + EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition competition = next((e for e in EVENTS if e.id == event_id), None) if not competition: await callback.answer("Мероприятие не найдено!") return - user_selections[callback.from_user.id] = { - "event_id": event_id, - "selected": set(), - "competition_name": competition.name, - } - + await state.update_data( + event_id=event_id, + selected=set(), + competition_name=competition.name, + current_page=0 + ) + await state.set_state(MarkStudentsState.selecting_students) + # Отправляем первую страницу студентов - await show_students_page(callback, page=0) - - -async def show_students_page(callback: CallbackQuery, page: int): - user_data = user_selections.get(callback.from_user.id) - if not user_data: - await callback.answer("Сессия устарела, начните заново!") - return + await show_students_page(callback, state) +async def show_students_page(callback: CallbackQuery, state: FSMContext): + data = await state.get_data() + selected = data.get("selected", set()) + current_page = data.get("current_page", 0) + # Разбиваем студентов на страницы по 5 человек students_per_page = 5 - start_idx = page * students_per_page + start_idx = current_page * students_per_page + STUDENTS = UserDao.get_all_students() # список объектов Student page_students = STUDENTS[start_idx : start_idx + students_per_page] total_pages = (len(STUDENTS) + students_per_page - 1) // students_per_page builder = InlineKeyboardBuilder() for student in page_students: - status = "✅" if student.id in user_data["selected"] else "◻️" + status = "✅" if student.id in selected else "◻️" builder.button( text=f"{status} {student.full_name} ({student.username})", - callback_data=f"toggle_{student.id}_{page}", + callback_data=f"toggle_{student.id}", ) # Кнопки навигации nav_buttons = [] - if page > 0: + if current_page > 0: nav_buttons.append( - InlineKeyboardButton(text="◀️ Назад", callback_data=f"page_{page-1}") + InlineKeyboardButton(text="◀️ Назад", callback_data="page_prev") ) - if (page + 1) * students_per_page < len(STUDENTS): + if (current_page + 1) * students_per_page < len(STUDENTS): nav_buttons.append( - InlineKeyboardButton(text="▶️ Вперед", callback_data=f"page_{page+1}") + InlineKeyboardButton(text="▶️ Вперед", callback_data="page_next") ) nav_buttons.append( InlineKeyboardButton(text="⏭️ Подтвердить", callback_data="confirm") @@ -89,36 +88,42 @@ async def show_students_page(callback: CallbackQuery, page: int): builder.row(*nav_buttons) await callback.message.edit_text( - f"Выберите студентов для мероприятия {user_data['competition_name']} [Страница {page+1} из {total_pages}]", + f"Выберите студентов для мероприятия {data['competition_name']} [Страница {current_page+1} из {total_pages}]", reply_markup=builder.as_markup(), ) - -@mark_students_router.callback_query(F.data.startswith("toggle_")) -async def toggle_student(callback: CallbackQuery): - _, student_id, page = callback.data.split("_") - student_id = int(student_id) - page = int(page) - - selected = user_selections[callback.from_user.id]["selected"] +@mark_students_router.callback_query(F.data.startswith("toggle_"), StateFilter(MarkStudentsState.selecting_students)) +async def toggle_student(callback: CallbackQuery, state: FSMContext): + student_id = int(callback.data.split("_")[1]) + data = await state.get_data() + selected = set(data.get("selected", set())) + if student_id in selected: selected.remove(student_id) else: selected.add(student_id) - - await show_students_page(callback, page) - - -@mark_students_router.callback_query(F.data.startswith("page_")) -async def change_page(callback: CallbackQuery): - page = int(callback.data.split("_")[1]) - await show_students_page(callback, page) - - -@mark_students_router.callback_query(F.data == "confirm") -async def confirm_selection(callback: CallbackQuery): - user_data = user_selections[callback.from_user.id] - selected_ids = user_data["selected"] + + await state.update_data(selected=selected) + await show_students_page(callback, state) + +@mark_students_router.callback_query(F.data.in_(["page_prev", "page_next"]), StateFilter(MarkStudentsState.selecting_students)) +async def change_page(callback: CallbackQuery, state: FSMContext): + data = await state.get_data() + current_page = data.get("current_page", 0) + + if callback.data == "page_prev": + current_page -= 1 + else: + current_page += 1 + + await state.update_data(current_page=current_page) + await show_students_page(callback, state) + +@mark_students_router.callback_query(F.data == "confirm", StateFilter(MarkStudentsState.selecting_students)) +async def confirm_selection(callback: CallbackQuery, state: FSMContext): + data = await state.get_data() + selected_ids = data.get("selected", set()) + STUDENTS = UserDao.get_all_students() # список объектов Student selected_students = [ f"{s.full_name} ({s.username})" for s in STUDENTS if s.id in selected_ids @@ -132,34 +137,32 @@ async def confirm_selection(callback: CallbackQuery): builder.button(text="✅ Подтвердить посещения", callback_data="final_confirm") builder.button(text="❌ Отменить", callback_data="cancel") + await state.set_state(MarkStudentsState.confirmation) await callback.message.edit_text( - f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{user_data['competition_name']}\":\n\n" + f"Вы выбрали {len(selected_students)} студентов для мероприятия \"{data['competition_name']}\":\n\n" + "\n".join(selected_students), reply_markup=builder.as_markup(), ) - -@mark_students_router.callback_query(F.data == "final_confirm") -async def final_confirmation(callback: CallbackQuery): - user_data = user_selections[callback.from_user.id] +@mark_students_router.callback_query(F.data == "final_confirm", StateFilter(MarkStudentsState.confirmation)) +async def final_confirmation(callback: CallbackQuery, state: FSMContext): + data = await state.get_data() try: with get_db() as db: participation_dao = ParticipationDAO(db) participation_dao.mark_participation( - user_data["event_id"], list(user_data["selected"]) + data["event_id"], list(data["selected"]) ) await callback.message.edit_text( - f"Готово! {len(user_data['selected'])} студентов отмечены на мероприятии \"{user_data['competition_name']}\"." + f"Готово! {len(data['selected'])} студентов отмечены на мероприятии \"{data['competition_name']}\"." ) - del user_selections[callback.from_user.id] + await state.clear() except Exception as e: await callback.answer(f"Ошибка: {str(e)}") - -@mark_students_router.callback_query(F.data == "cancel") -async def cancel_action(callback: CallbackQuery): - if callback.from_user.id in user_selections: - del user_selections[callback.from_user.id] - await callback.message.edit_text("Действие отменено.") +@mark_students_router.callback_query(F.data == "cancel", StateFilter(MarkStudentsState.confirmation)) +async def cancel_action(callback: CallbackQuery, state: FSMContext): + await state.clear() + await callback.message.edit_text("Действие отменено.") \ No newline at end of file diff --git a/bot/main.py b/bot/main.py index 95ea499..277a11a 100644 --- a/bot/main.py +++ b/bot/main.py @@ -42,6 +42,7 @@ command="/missed_deadlines", description="Посмотреть пропущенные дедлайны" ), types.BotCommand(command="/add_competition", description="Создать мероприятие"), + types.BotCommand(command="/mark_students", description="Отметить присутствующих"), ] dp.include_routers( diff --git a/bot/states/mark_students_states.py b/bot/states/mark_students_states.py new file mode 100644 index 0000000..d6155c5 --- /dev/null +++ b/bot/states/mark_students_states.py @@ -0,0 +1,7 @@ +from aiogram.fsm.state import State, StatesGroup + + +class MarkStudentsState(StatesGroup): + selecting_event = State() + selecting_students = State() + confirmation = State() \ No newline at end of file diff --git a/bot/tasks.py b/bot/tasks.py index d3019e2..47699fc 100644 --- a/bot/tasks.py +++ b/bot/tasks.py @@ -34,7 +34,7 @@ async def sync_education_tasks(bot: Bot): user.lives -= 1 user.violations += 1 task.violation_recorded = ( - True # Отмечаем, что нарушение обработано + True ) teacher_message = ( f"Задача {task.name} истека у студента {user}." From b35f992f2a1202c91f05c22d067d3958390d87b6 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Sun, 1 Jun 2025 20:19:48 +0300 Subject: [PATCH 11/12] delimited the level of access --- bot/middlewares.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bot/middlewares.py b/bot/middlewares.py index 3306216..12c1212 100644 --- a/bot/middlewares.py +++ b/bot/middlewares.py @@ -5,6 +5,7 @@ from database.db import get_db from database.user_dao import UserDAO from logging import getLogger +from settings import Config logger = getLogger() @@ -53,5 +54,15 @@ async def __call__( return data["user"] = user + + # Список админских команд + admin_commands = ["/add_competition", "/mark_students", "/add_task"] + + # Если команда админская, проверяем права + if event.text and any(event.text.startswith(cmd) for cmd in admin_commands): + teacher_ids = [int(id) for id in Config()._teacher_ids.split(",")] + if event.from_user.id not in teacher_ids: + await event.answer("⛔ У вас нет прав для выполнения этой команды") + return return await handler(event, data) From ca8cf9edd21eb7ae36d854e2d4261b9d234da0d4 Mon Sep 17 00:00:00 2001 From: DarkMK69 Date: Sun, 1 Jun 2025 20:21:27 +0300 Subject: [PATCH 12/12] =?UTF-8?q?=D1=82=D1=80=D0=B0=D0=B1=D0=BB=D1=8B=20?= =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot/handlers/marking_students.py | 53 +++++++++++++++++++++--------- bot/middlewares.py | 4 +-- bot/states/mark_students_states.py | 2 +- bot/tasks.py | 4 +-- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/bot/handlers/marking_students.py b/bot/handlers/marking_students.py index 8742b41..bf9080e 100644 --- a/bot/handlers/marking_students.py +++ b/bot/handlers/marking_students.py @@ -16,6 +16,7 @@ UserDao = UserDAO(db) CompetitionDAO = CompetitionDao(db) + @mark_students_router.message(Command("mark_students")) async def show_events(message: Message, state: FSMContext): builder = InlineKeyboardBuilder() @@ -26,11 +27,14 @@ async def show_events(message: Message, state: FSMContext): callback_data=f"event_{event.id}", ) builder.adjust(1) - + await state.set_state(MarkStudentsState.selecting_event) await message.answer("Выберите мероприятие:", reply_markup=builder.as_markup()) -@mark_students_router.callback_query(F.data.startswith("event_"), StateFilter(MarkStudentsState.selecting_event)) + +@mark_students_router.callback_query( + F.data.startswith("event_"), StateFilter(MarkStudentsState.selecting_event) +) async def select_students(callback: CallbackQuery, state: FSMContext): event_id = int(callback.data.split("_")[1]) EVENTS = CompetitionDAO.get_all_competition() # список объектов Competition @@ -44,18 +48,19 @@ async def select_students(callback: CallbackQuery, state: FSMContext): event_id=event_id, selected=set(), competition_name=competition.name, - current_page=0 + current_page=0, ) await state.set_state(MarkStudentsState.selecting_students) - + # Отправляем первую страницу студентов await show_students_page(callback, state) + async def show_students_page(callback: CallbackQuery, state: FSMContext): data = await state.get_data() selected = data.get("selected", set()) current_page = data.get("current_page", 0) - + # Разбиваем студентов на страницы по 5 человек students_per_page = 5 start_idx = current_page * students_per_page @@ -88,38 +93,48 @@ async def show_students_page(callback: CallbackQuery, state: FSMContext): builder.row(*nav_buttons) await callback.message.edit_text( - f"Выберите студентов для мероприятия {data['competition_name']} [Страница {current_page+1} из {total_pages}]", + f"Выберите студентов для мероприятия {data['competition_name']} [Страница {current_page + 1} из {total_pages}]", reply_markup=builder.as_markup(), ) -@mark_students_router.callback_query(F.data.startswith("toggle_"), StateFilter(MarkStudentsState.selecting_students)) + +@mark_students_router.callback_query( + F.data.startswith("toggle_"), StateFilter(MarkStudentsState.selecting_students) +) async def toggle_student(callback: CallbackQuery, state: FSMContext): student_id = int(callback.data.split("_")[1]) data = await state.get_data() selected = set(data.get("selected", set())) - + if student_id in selected: selected.remove(student_id) else: selected.add(student_id) - + await state.update_data(selected=selected) await show_students_page(callback, state) -@mark_students_router.callback_query(F.data.in_(["page_prev", "page_next"]), StateFilter(MarkStudentsState.selecting_students)) + +@mark_students_router.callback_query( + F.data.in_(["page_prev", "page_next"]), + StateFilter(MarkStudentsState.selecting_students), +) async def change_page(callback: CallbackQuery, state: FSMContext): data = await state.get_data() current_page = data.get("current_page", 0) - + if callback.data == "page_prev": current_page -= 1 else: current_page += 1 - + await state.update_data(current_page=current_page) await show_students_page(callback, state) -@mark_students_router.callback_query(F.data == "confirm", StateFilter(MarkStudentsState.selecting_students)) + +@mark_students_router.callback_query( + F.data == "confirm", StateFilter(MarkStudentsState.selecting_students) +) async def confirm_selection(callback: CallbackQuery, state: FSMContext): data = await state.get_data() selected_ids = data.get("selected", set()) @@ -144,7 +159,10 @@ async def confirm_selection(callback: CallbackQuery, state: FSMContext): reply_markup=builder.as_markup(), ) -@mark_students_router.callback_query(F.data == "final_confirm", StateFilter(MarkStudentsState.confirmation)) + +@mark_students_router.callback_query( + F.data == "final_confirm", StateFilter(MarkStudentsState.confirmation) +) async def final_confirmation(callback: CallbackQuery, state: FSMContext): data = await state.get_data() @@ -162,7 +180,10 @@ async def final_confirmation(callback: CallbackQuery, state: FSMContext): except Exception as e: await callback.answer(f"Ошибка: {str(e)}") -@mark_students_router.callback_query(F.data == "cancel", StateFilter(MarkStudentsState.confirmation)) + +@mark_students_router.callback_query( + F.data == "cancel", StateFilter(MarkStudentsState.confirmation) +) async def cancel_action(callback: CallbackQuery, state: FSMContext): await state.clear() - await callback.message.edit_text("Действие отменено.") \ No newline at end of file + await callback.message.edit_text("Действие отменено.") diff --git a/bot/middlewares.py b/bot/middlewares.py index 12c1212..3159c03 100644 --- a/bot/middlewares.py +++ b/bot/middlewares.py @@ -54,10 +54,10 @@ async def __call__( return data["user"] = user - + # Список админских команд admin_commands = ["/add_competition", "/mark_students", "/add_task"] - + # Если команда админская, проверяем права if event.text and any(event.text.startswith(cmd) for cmd in admin_commands): teacher_ids = [int(id) for id in Config()._teacher_ids.split(",")] diff --git a/bot/states/mark_students_states.py b/bot/states/mark_students_states.py index d6155c5..678beed 100644 --- a/bot/states/mark_students_states.py +++ b/bot/states/mark_students_states.py @@ -4,4 +4,4 @@ class MarkStudentsState(StatesGroup): selecting_event = State() selecting_students = State() - confirmation = State() \ No newline at end of file + confirmation = State() diff --git a/bot/tasks.py b/bot/tasks.py index 47699fc..00c9edb 100644 --- a/bot/tasks.py +++ b/bot/tasks.py @@ -33,9 +33,7 @@ async def sync_education_tasks(bot: Bot): ): user.lives -= 1 user.violations += 1 - task.violation_recorded = ( - True - ) + task.violation_recorded = True teacher_message = ( f"Задача {task.name} истека у студента {user}." )