Не могу разобраться с созданием динамической клавиатуры и её обработки (aiogram 3)
Поскольку я сам учу библиотеку с нуля при помощи youtube+chatgpt то столкнувшись с тупиком я не могу из него выбраться. Я не смог найти решение своей проблемы и chatgpt не смог мне помочь.
Собственно мой первый учебный проект который для меня придумал chatgpt с целью обучения - бот для управления задачами в телеграме. Всё было относительно понятно, до тех пор пока я не начал делать последнюю кнопку, которая должна была быть под каждым сообщением с задачей в списке задач. Несколько дней потратил на то что бы просто прикрутить кнопки под каждое сообщение динамически, в итоге я еле еле смог их сделать с помощью обычного цикла, но получилось так что это была просто одна и та же кнопка но под каждым сообщением как я и хотел, единственное что я хотел но не смог реализовать - что бы кнопка принадлежала именно к той задаче под которой она находится. Спустя несколько дней страданий я всё же смог сделать так что бы задача удалялась из базы данных[![введите сюда описание изображения][1]][1], но загвоздка в том что любая кнопка которая была нажата под любой задачей удаляла задачу и именно последнюю. Потом я понял и попробовал взять id из базы и подставить в callback самой кнопки сделав вывод сообщений убедился что в принципе это то что и нужно[![введите сюда описание изображения][2]][2]. Последняя проблема которую я не смог решить это обработка через callback, я не могу понять как в асинхронный хендлер который ловит эти коллбеки передать точно такой же айди кнопки который привязан именно к той кнопки которая нажимается, при попытке передать напрямую переменную она не читается
Я хочу понять как правильно передавать переменные между функциями и файлами, как из одной функции получилось доступ в другой функции другого файла Можно ли передавать третьим аргументом дополнительное значение в router функцию и если нет то тогда как это сделать? @router.callback_query(F.data == f"delete_task[20]") async def del_task(callback:CallbackQuery,третий_аргумент?): await callback.message.answer(f"Задача с ID 20 удалена") await db.delete_task("20") [1]: https://i.sstatic.net/GPv6zO8Q.png [2]: https://i.sstatic.net/DTEESq4E.png
сам код:
handlers.py:
import asyncio
from aiogram import F, Router
from aiogram.types import Message, CallbackQuery
from aiogram.filters import Command, CommandStart, CommandObject
import keyboards as kb
from aiogram.fsm.state import StatesGroup, State
from aiogram.fsm.context import FSMContext
import bot_db as db
from bot_db import check_list
class Task(StatesGroup):
title = State()
content = State()
router = Router()
@router.message(CommandStart())
async def start(message:Message):
await message.answer(
"Привет! Я бот для управления задачами. Вот что я могу:\n"
"✍Добавить новую задачу.\n"
"?Просмотреть все задачи.\n"
"?Удалить задачу по ID.\n"
"?Пометить задачу как выполненную.\n"
"Используй бота, чтобы легко управлять своими задачами!", reply_markup=kb.main
)
@router.message(F.data == "home")
async def back(callback:CallbackQuery):
await callback.message.answer("/start")
#добавление, fsm и внесение в базу
@router.message(F.text == ("Добавить новую задачу"))
async def reg_one(message:Message, state:FSMContext):
await state.set_state(Task.title)
await message.answer("Введите заголовок задачи")
@router.message(Task.title)
async def reg_two(message:Message, state:FSMContext):
await state.update_data(title=message.text)
await state.set_state(Task.content)
await message.answer("Введите содержимое задачи")
@router.message(Task.content)
async def reg_three(message:Message, state:FSMContext):
await state.update_data(content=message.text)
data = await state.get_data()
await message.answer(f"Задача добавлена.\n{data['title']}\n{data['content']}")
await db.db_start(data["title"], data["content"])
await state.clear()
@router.message(F.text == "Просмотреть все задачи")
async def check_task(message:Message):
global task_list
task_list = await db.check_list()
if not task_list:
await message.answer("Список задач пуст.")
return
else:
for task in task_list:
global task_id
task_id, task_title, task_content = task
await message.answer(f"Задача:\n?ID: {str(task_id)} | TITLE:={str(task_title)} | CONTENT: {str(task_content)}", reply_markup=await kb.delete(task_id))
@router.callback_query(F.data == f"delete_task[20]")
async def del_task(callback:CallbackQuery):
await callback.message.answer(f"Задача с ID 20 удалена")
await db.delete_task("20")
keyboards.py:
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.filters import callback_data
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
main = ReplyKeyboardMarkup(
keyboard=[
[
KeyboardButton(text="Добавить новую задачу"), KeyboardButton(text="Просмотреть все задачи")
],
],
resize_keyboard=True,
one_time_keyboard=False,
selective=True,
input_field_placeholder="Выберите действие"
)
async def delete(task_id):
del_key = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text=f"Удалить задачу[{task_id}]", callback_data=f"delete_task[{task_id}]")
]
]
)
return del_key
bot_db.py:
import asyncio
db = sqlite3.connect("data_bot.db")
c = db.cursor()
#c.execute("SELECT * FROM task")
#print(c.fetchall())
async def db_start(title:str, content:str):
c.execute("INSERT INTO task (title, content) VALUES (?, ?)", (title, content))
db.commit()
async def check_list():
c.execute("SELECT * FROM task")
task_list = c.fetchall()
return task_list
async def delete_task(task_id:int):
c.execute("DELETE FROM task WHERE id = ?", (task_id,))
db.commit()
Ответы (1 шт):
Если я правильно вас понял, вы хотите передать ID
записи для удаления в handler
, а конкретно в строчке @router.callback_query(F.data == f"delete_task[20]")
?
Вы можете написать свой класс наследуясь от CallbackData
добавив в него еще одно поле, чтобы вам в handler
прилетал ID
вашей записи.
keyboards.py:
from aiogram.filters.callback_data import CallbackData
class TaskCallback(CallbackData, prefix='tasks'):
action: str
value: str
async def delete_keyboard(task_id):
del_key = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=f"Удалить задачу [{task_id}]",
callback_data=TaskCallback(
action='delete_task', value=task_id
).pack()
)
]
]
)
return del_key
Теперь вам нужно будет добавить handler
который будет реагировать на кнопку, и уже из этого handler'a
вызывать функцию удаления записи
handlers.py:
from keyboards import TaskCallback
from bot_db import delete_task
@router.callback_query(TaskCallback.filter(F.action == "delete_task"))
async def delete_task_handler(query: CallbackQuery, callback_data: TaskCallback):
task_id = int(callback_data.value) # В документации указано, что значение будет упаковано в строку.
await delete_task(task_id)
await query.answer('Запись успешно удалена!')
Если не ошибаюсь, когда вы пишете свой класс Callback
, у него есть некоторые ограничения, передаваемые данные не должны превышать "Сколько то там байт", поэтому в некоторых местах я делал немного по другому:
keyboards.py:
InlineKeyboardButton(
text=f"Удалить задачу [{task_id}]",
callback_data=f"delete_task:{task_id}"
)
handlers.py:
# Тут мы просто делим прилетевший callback по делителю и проверяем соответствие
@router.callback_query(F.data.split(':')[0] == 'delete_task')
async def delete_task_handler(call: CallbackQuery):
task_id = int(call.data.split(':')[1] # ваш айди
await delete_task(task_id)
await query.answer('Запись успешно удалена!')
В принципе TaskCallback
будет хранить в себе точно такие же данные, но при втором варианте почему то ошибку с превышением количества байт в запросе я не ловил.