Перейти к содержанию

Генерация inline клавиатур

В прошлом отчете о своей работе над Telegram ботом Work for Everyone я поведал себе и вам о создании основы для работы с данными. В частности, создал таблицу, в которую загрузил данные регионов. Эти данные будут использоваться для генерации клавиатуры, а также для иных целей.

Постановка задачи

Первая inline клавиатура мне нужна в хендлере, который отвечает за обработку нажатий кнопок «Готов!» и «Начать ввод данных».

Вторая inline клавиатура мне понадобится в хендлере, который перехватывает выбранный пользователем федеральный округ и в ответ предлагает выбрать регион, в котором пользователь хотел бы посмотреть доступные вакансии для людей с инвалидностью. Как раз названия регионов и будут выступать в качестве inline кнопок клавиатуры.

У кнопок данных клавиатур много общего. Так, в качестве текста для кнопки используется название федерального округа и региона, а в качестве callback_data – их номера. А если есть у двух сценариев (генерация клавиатур) что-то общее, надо этим общим воспользоваться. Вот я и напишу одну функцию для генерации двух клавиатур.

Реализация

Для создания клавиатур с inline кнопками буду использовать классы InlineKeyboardButton (для создания непосредственно кнопок) и InlineKeyboardBuilder (для построения клавиатуры). Функция должна вернуть объект класса InlineKeyboardBuilder.

И уже в хендлере при передаче клавиатуры пользователю буду использовать метод as_markup(), который преобразует объект билдера в объект класса InlineKeyboardMarkup. Безусловно, метод as_markup() можно было бы использовать непосредственно в функции генерации клавиатур, но мне этот вариант не подходит, так как в клавиатуру с регионами нужно будет добавить кнопку возврата к выбору федеральных округов, на случай, если пользователь неверно выберет округ.

В качестве аргумента функция принимает список кортежей с необходимыми данными.

Таким образом, мне нужно, кроме самой функции генерации клавиатур, правильно подготовить данные.

Подготовка данных для генерации клавиатур

Как я уже писал ранее, данные о федеральных округах у меня хранятся в словаре. У словаря есть метод items(), который возвращает специальный объект dict_items (набор кортежей с парами ключ — значение). Если этот метод применить к словарю с данными федеральных округов, то получим:

dict_items(
    [
        ('Центральный федеральный округ', 30),
        ('Северо-Западный федеральный округ', 31),
        ('Приволжский федеральный округ', 33),
        ('Уральский федеральный округ', 34),
        ('Северо-Кавказский федеральный округ', 38),
        ('Южный федеральный округ', 40),
        ('Сибирский федеральный округ', 41),
        ('Дальневосточный федеральный округ', 42)
    ]
)

Собственно это практически то, что нужно. Объект dict_items внутри себя содержит список кортежей. Сам объект является итерируемым. На каждой итерации можно получить кортеж. Кстати, можно, используя функцию list(), преобразовать объект dict_items в обычный список кортежей, что собственно я и сделаю, чтобы тип входных данных в функцию генерации был равнозначный во всех случаях её использования.

Данные для клавиатур с названием регионов хранятся в базе. Каждая inline клавиатура будет состоять из кнопок с названиями регионов в выбранном пользователем федеральном округе.

В функцию генерации клавиатур я должен передать список кортежей с названием регионов и их номерами.

Для реализации этого шага мне понадобится функция, которая будет делать запрос в базу данных для получения списка регионов в конкретном федеральном округе.

Назову функцию get_data_regions(). В качестве аргумента функция должна принимать номер федерального округа, который выбрал пользователь. Функция возвращает список кортежей.

def get_data_regions(federal_district_code: int) -> List[tuple[str, int]]:
    """
    Получение списка регионов заданного пользователем федерального округа.
    """
    _database_connection()
    data: ModelSelect = (
        Region.select(Region.region_name, Region.region_code)
        .where(Region.federal_district_code == federal_district_code)
        .tuples()
    )
    _database_close()
    if data:
        return [region_data for region_data in data]
    return False

Буквально пару слов о том, что происходит. В методе select() я указываю поля, которые мне нужно получить из таблицы region. Методом where() задаю параметры поиска нужных регионов, а метод tuples() позволяет вернуть данные в виде кортежей.

Возвращаемый на запрос к БД объект ModelSelect является итерируемым, поэтому его также, как и объект dict_items можно сразу передать в функцию генерации клавиатур, но я так делать не буду. Возвращая результат, я, используя списковые включения, сгенерирую список кортежей, который в качестве аргумента и будет передан в функцию генерации клавиатур.

Все данные подготовлены. Собственно вот код самой функции генерации клавиатур.

def generation_kb(data_list: List[tuple[str, int]]) -> InlineKeyboardBuilder:
    """
    Функция генерации клавиатур с inline кнопками
    для федеральных округов и регионов.
    """
    bt_list = [
        InlineKeyboardButton(text=x[0], callback_data=str(x[1]))
        for x in data_list
    ]
    builder = InlineKeyboardBuilder().row(*bt_list, width=1)
    return builder

Внезапная задачка

Долго пытался решить следующую задачу. Изначально ширину клавиатуры я задал равной 2, как это делал изначально, когда работал со «стандартными» кнопками. Но текст на inline кнопках никак не хотел переноситься и просто срезался, что, конечно, меня не могло устроить.

Гугление не принесло положительных результатов. Документация также не дала ответа (может, плохо искал). В итоге, где-то нашел информацию, что такое поведение как перенос текста inline кнопки на новую строку невозможно. Так я и пришел к решению, что ширина должна быть равна единичке.

Вот такие клавиатуры в итоге получились.

Inline клавиатура выбора федеральных округов

Скриншот диалога с ботом. Список федеральных округов.

Inline клавиатура выбора региона

Скриншот диалога с ботом. Список регионов в выбранном федеральном округе (центральный ФО).

На этом на сегодня все, в следующий раз поговорим о машине состояний и фильтрации. Подписывайтесь на мой Telegram канал Touch IT.

Код проекта Telegram bot Work for everyone доступен на GitHub.

Я открыт к любой, даже самой жесткой, но конструктивной критике!