Гайд "Динамичная" Inline клавиатура для вывода ваших предложений. aiogram

Пользователь
Статус
Оффлайн
Регистрация
24 Авг 2019
Сообщения
239
Реакции[?]
37
Поинты[?]
0
Недавно столкнулся с нуждой реализовать вывод каталога. Делать это сообщениями слишком костыльно. А вот вариант вывода инлайн клавиатуры с 10 позициями и дальнейшем их пролистывании звучит довольно привлекательно.
Получение данных происходит с MongoDB. Код самой клавиатуры:
offers keyboard:
from aiogram.types import  InlineKeyboardMarkup, InlineKeyboardButton
def offers_kb(posts, n): #Передаем массив всех товаров и желаемый индекс последнего выводимого товара(по факту "строить массив" для желаемого индекса можно не в самой функции, но как по мне логически правильнее будет так)
    offers_kb = InlineKeyboardMarkup()
    for i in range(n-10, len(posts)):#Так как я решил выводить только 10 позиций за раз, то от конечно индекса отнимаю 10
        if i >= n or i > len(posts):#Проверка на то когда нужно прекращать добавлять кнопки(когда индекс больше передаваемого, чтобы не выводилось больше позиций чем нужно. И проверка на то, если у последней страницы не хватает "добить" 10 позиций, оно не крашилось изза выхода за пределы массива)
            break
        else:
            cur = InlineKeyboardButton(str(posts[i]["count"]), callback_data="offer_id:"+str(posts[i]["_id"]))#Создаю кнопку в тексте которой будет отображаться количество позиции(так как до этого человек уже выбрал что именно он хочет купить), а в callback_data закидываю id продукта из бд для дальнейшего взаимодействия
            offers_kb.add(cur)#Добавляю в клавиатуру
    if n <= 10 and n >= len(posts):#Здесь идут проверки для добавления кнопок назад/вперед чтобы в конце списка не появлялась кнопка вперед
        cancel = InlineKeyboardButton("Cancel", callback_data="cancel_offers")
        offers_kb.row(cancel)
    elif n == 10:
        forward = InlineKeyboardButton("Вперед", callback_data = "forward_offers" )
        cancel = InlineKeyboardButton("Cancel", callback_data="cancel_offers")
        offers_kb.row(forward)
        offers_kb.row(cancel)
    elif n>=len(posts):
        back= InlineKeyboardButton("Назад", callback_data="back_offers" )
        cancel = InlineKeyboardButton("Cancel", callback_data="cancel_offers")
        offers_kb.row(back)
        offers_kb.row(cancel)
    else:
        forward = InlineKeyboardButton("Вперед", callback_data = "forward_offers" )
        back= InlineKeyboardButton("Назад", callback_data="back_offers" )
        cancel = InlineKeyboardButton("Cancel", callback_data="cancel_offers")
        offers_kb.row(back, forward)
        offers_kb.row(cancel)
    return offers_kb#Возвращаем созданную клавиатуру
Функция где используется данная клавиатура выглядит так:

buy function:
from aiogram.dispatcher import FSMContext
from aiogram import types
from pymongo.database import Database
from aiogram.dispatcher.filters.state import State, StatesGroup

from keyboards.offers import offers_kb

class buy_states(StatesGroup):
    cur_list = State()
    
async def buy(call:types.CallbackQuery, state:FSMContext, db:Database, n = 10):#При первом вызове не указываем n
    data = await state.get_data()
    offers = []
    for offer in db["some_pr"].find({"game":data.get("game")}).sort("cost_per_one"):#сортировка по цене
        offers.append(offer)#прикол mongo, что он возвращает не массив а какую то залупу
    
    await buy_states.cur_list.set()
    await state.update_data(cur_list = n)#Для отслеживания конечной позиции
    await call.message.answer("Вот все наши предложения:", reply_markup=offers_kb(offers, n))
Эту функцию просто вызываем в нужном хэндлере.
Затем нужно реализовать кнопки вперед/назад/отмена:
Добавляем новый хэндлер:

bot.py:
@dp.callback_query_handler(lambda c: c.data.endswith("_offers"), state=buy_states.cur_list)#чтобы не писать отдельные хэндлеры для каждой функции, у каждой кнопке в каллбэк дате было дописано _offers, что мы здесь и проверяем. А также проверяем что state=cur_list(засетили его при первом вызове функции)
async def offers_process(call:types.CallbackQuery, state:FSMContext):
    data = await state.get_data()
    match call.data.replace("_offers", ""):
        case "forward":
            _cur_list = data.get("cur_list") + 10#нажата кнопка вперед, значит к желаемому индексу последнего товара прибавляем 10
        case "back":
            _cur_list = data.get("cur_list") - 10#аналогично, только теперь отнимаем
        case "cancel":#если отмена, то завершаем state и удаляем сообщение с каталогом
            await state.finish()
            await call.message.delete()
            return
    
    offers = []
    for offer in db[data.get("game_type").replace("cat_", "")].find({"game":data.get("game")}).sort("cost_per_one"):
        offers.append(offer) #опять получаем массив
    
    await state.update_data(cur_list = _cur_list)#вносим новый индекс
    await call.message.edit_reply_markup(offers_kb(offers, _cur_list))#и для плавности работы не переотправляем сообщение, а просто изменяем уже отправленное сообщение
Чтобы получить данные при нажатии на товар нужно реализовать еще один хэндлер:

bot.py:
from bson.objectid import ObjectId
client = MongoClient(MONGO_API)
db = client["test_db"] #подключение к бд(просто написать две эти строчки в основном файле бота перед самой его инициализацией)

bot = Bot(token=BOT_TOKEN)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)

@dp.callback_query_handler(lambda c: c.data.startswith("offer_id:"), state=buy_states.cur_list)
async def offer_process(call:types.CallbackQuery, state:FSMContext):
    data = await state.get_data()
    
    offer = db[data.get("game_type").replace("cat_", "")].find_one({"_id":ObjectId(call.data.replace("offer_id:", ""))})
    await call.message.answer(f"Наименование: {offer["name"]}")#Ну а тут просто отправляем расширеннцю информацию о предложении, прикрепляя кнопки оплаты, чата и тд
Все, рабочий и относительно визуально плавный каталог работает
 
ставь чайник, зажигай плиту
Эксперт
Статус
Оффлайн
Регистрация
22 Май 2020
Сообщения
1,444
Реакции[?]
1,092
Поинты[?]
10K
9. Не забывай о читаемости кода - добавляй комментарии, разбивай код на отдельные функции и модули, используйте хорошие имена переменных и функций.
Комментарии != читаемость кода. В большинстве правильных проектов, максимум, что можно найти - докстринги. Читаемым код делают правильно названные перменные, функции, константы, правильно используемые области переменных и т.д.

2. Спорно, кому как более удобно, дело вкуса
Нет. Магические числа всегда хуйня.
 
ставь чайник, зажигай плиту
Эксперт
Статус
Оффлайн
Регистрация
22 Май 2020
Сообщения
1,444
Реакции[?]
1,092
Поинты[?]
10K
С каких пор, константы стали магическими числами.
Я где-то говорил, что константы это магические числа?

Плохой код:
some_list = items[:10]

Хороишй код:
ITEMS_PER_PAGE = 10
items_page = items[:ITEMS_PER_PAGE]
 
Участник
Статус
Оффлайн
Регистрация
26 Июн 2020
Сообщения
1,117
Реакции[?]
210
Поинты[?]
8K
Я где-то говорил, что константы это магические числа?

Плохой код:
some_list = items[:10]

Хороишй код:
ITEMS_PER_PAGE = 10
items_page = items[:ITEMS_PER_PAGE]
1678448931029.png
Скорее, я тебя неправильно понял
 
Сверху Снизу