├── lesson_3
├── handlers
│ ├── admin_private.py
│ └── user_private.py
└── app.py
├── lesson_4
├── handlers
│ ├── admin_private.py
│ ├── user_group.py
│ └── user_private.py
├── common
│ └── bot_cmds_list.py
├── filters
│ └── chat_types.py
└── app.py
├── lesson_5
├── handlers
│ ├── admin_private.py
│ ├── user_group.py
│ └── user_private.py
├── common
│ └── bot_cmds_list.py
├── filters
│ └── chat_types.py
├── app.py
└── kbds
│ └── reply.py
├── lesson_6-FSM
├── common
│ ├── restricted_words.py
│ └── bot_cmds_list.py
├── filters
│ └── chat_types.py
├── app.py
├── kbds
│ └── reply.py
└── handlers
│ ├── user_group.py
│ ├── user_private.py
│ └── admin_private.py
├── lesson_7
├── common
│ ├── restricted_words.py
│ └── bot_cmds_list.py
├── requirements.txt
├── README.md
├── filters
│ └── chat_types.py
├── database
│ ├── models.py
│ ├── engine.py
│ └── orm_query.py
├── middlewares
│ └── db.py
├── kbds
│ ├── inline.py
│ └── reply.py
├── app.py
└── handlers
│ ├── user_group.py
│ ├── user_private.py
│ └── admin_private.py
├── lesson_8 template
├── common
│ ├── restricted_words.py
│ ├── bot_cmds_list.py
│ └── texts_for_db.py
├── requirements.txt
├── banners
│ ├── about.jpg
│ ├── cart.jpg
│ ├── main.jpg
│ ├── catalog.jpg
│ ├── payment.jpg
│ └── shipping.jpg
├── kbds
│ ├── inline.py
│ └── reply.py
├── filters
│ └── chat_types.py
├── database
│ ├── engine.py
│ ├── models.py
│ └── orm_query.py
├── middlewares
│ └── db.py
├── handlers
│ ├── user_private.py
│ ├── user_group.py
│ └── admin_private.py
├── app.py
└── README.md
├── lesson_8_multi_level_inline_menu
├── common
│ ├── restricted_words.py
│ ├── bot_cmds_list.py
│ └── texts_for_db.py
├── requirements.txt
├── README.md
├── filters
│ └── chat_types.py
├── database
│ ├── engine.py
│ ├── models.py
│ └── orm_query.py
├── middlewares
│ └── db.py
├── kbds
│ ├── reply.py
│ └── inline.py
├── utils
│ └── paginator.py
├── app.py
└── handlers
│ ├── user_group.py
│ ├── user_private.py
│ ├── menu_processing.py
│ └── admin_private.py
├── lesson_6 -Prepared_template_for_review
├── common
│ ├── restricted_words.py
│ └── bot_cmds_list.py
├── filters
│ └── chat_types.py
├── app.py
├── kbds
│ └── reply.py
└── handlers
│ ├── user_group.py
│ ├── admin_private.py
│ └── user_private.py
├── README.md
├── lesson_2
└── app.py
└── lesson_1
└── app.py
/lesson_3/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lesson_4/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lesson_5/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lesson_6-FSM/common/restricted_words.py:
--------------------------------------------------------------------------------
1 | restricted_words = {"кабан", "хомяк", "выхухоль"}
--------------------------------------------------------------------------------
/lesson_7/common/restricted_words.py:
--------------------------------------------------------------------------------
1 | restricted_words = {"кабан", "хомяк", "выхухоль"}
--------------------------------------------------------------------------------
/lesson_8 template/common/restricted_words.py:
--------------------------------------------------------------------------------
1 | restricted_words = {"кабан", "хомяк", "выхухоль"}
--------------------------------------------------------------------------------
/lesson_7/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram
2 | python-dotenv
3 | sqlalchemy
4 | asyncpg
5 | aiosqlite
6 |
--------------------------------------------------------------------------------
/lesson_8 template/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram
2 | python-dotenv
3 | sqlalchemy
4 | asyncpg
5 | aiosqlite
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/common/restricted_words.py:
--------------------------------------------------------------------------------
1 | restricted_words = {"кабан", "хомяк", "выхухоль"}
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/common/restricted_words.py:
--------------------------------------------------------------------------------
1 | restricted_words = {"кабан", "хомяк", "выхухоль"}
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/requirements.txt:
--------------------------------------------------------------------------------
1 | aiogram==3.3.0
2 | sqlalchemy==2.0.25
3 | python-dotenv
4 | asyncpg
5 | aiosqlite
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/README.md:
--------------------------------------------------------------------------------
1 | Изображения баннеров в папке lesson_8 template.
2 | Код из видео yt канала Python Hub Studio.
3 |
--------------------------------------------------------------------------------
/lesson_8 template/banners/about.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/about.jpg
--------------------------------------------------------------------------------
/lesson_8 template/banners/cart.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/cart.jpg
--------------------------------------------------------------------------------
/lesson_8 template/banners/main.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/main.jpg
--------------------------------------------------------------------------------
/lesson_8 template/banners/catalog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/catalog.jpg
--------------------------------------------------------------------------------
/lesson_8 template/banners/payment.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/payment.jpg
--------------------------------------------------------------------------------
/lesson_8 template/banners/shipping.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PythonHubStudio/aiogram-3-course-telegram-bot/HEAD/lesson_8 template/banners/shipping.jpg
--------------------------------------------------------------------------------
/lesson_7/README.md:
--------------------------------------------------------------------------------
1 | Для работы через PostgreSQL, нужно установить сервер этой СУБД на ПК, или указать url адрес базы данных на удаленном сервере. Иначе, использовать engine на sqlite+aiosqlite.
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Код из каждого видео плейлиста в отдельной папке lesson_1, 2, .. и так далее
2 | Код из курса по созданию бота для Telegram на python с aiogram 3, YT канала Python Hub Studio
3 |
4 | Код дополнительного примера из второго видео, по работе с API Telegram напрямую через aiohttp:
5 | https://github.com/PythonHubStudio/Telegram-bot-with-aiohttp-and-asyncio
6 |
--------------------------------------------------------------------------------
/lesson_4/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import BotCommand
2 |
3 |
4 | private = [
5 | BotCommand(command='menu', description='Посмотреть меню'),
6 | BotCommand(command='about', description='О нас'),
7 | BotCommand(command='payment', description='Варианты оплаты'),
8 | BotCommand(command='shipping', description='Варианты доставки'),
9 | ]
--------------------------------------------------------------------------------
/lesson_5/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import BotCommand
2 |
3 |
4 | private = [
5 | BotCommand(command='menu', description='Посмотреть меню'),
6 | BotCommand(command='about', description='О нас'),
7 | BotCommand(command='payment', description='Варианты оплаты'),
8 | BotCommand(command='shipping', description='Варианты доставки'),
9 | ]
--------------------------------------------------------------------------------
/lesson_7/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import BotCommand
2 |
3 |
4 | private = [
5 | BotCommand(command='menu', description='Посмотреть меню'),
6 | BotCommand(command='about', description='О нас'),
7 | BotCommand(command='payment', description='Варианты оплаты'),
8 | BotCommand(command='shipping', description='Варианты доставки'),
9 | ]
--------------------------------------------------------------------------------
/lesson_6-FSM/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import BotCommand
2 |
3 |
4 | private = [
5 | BotCommand(command='menu', description='Посмотреть меню'),
6 | BotCommand(command='about', description='О нас'),
7 | BotCommand(command='payment', description='Варианты оплаты'),
8 | BotCommand(command='shipping', description='Варианты доставки'),
9 | ]
--------------------------------------------------------------------------------
/lesson_4/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
--------------------------------------------------------------------------------
/lesson_5/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
--------------------------------------------------------------------------------
/lesson_8 template/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | # from aiogram.types import BotCommand
2 |
3 |
4 | # private = [
5 | # BotCommand(command='menu', description='Посмотреть меню'),
6 | # BotCommand(command='about', description='О нас'),
7 | # BotCommand(command='payment', description='Варианты оплаты'),
8 | # BotCommand(command='shipping', description='Варианты доставки'),
9 | # ]
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import BotCommand
2 |
3 |
4 | private = [
5 | BotCommand(command='menu', description='Посмотреть меню'),
6 | BotCommand(command='about', description='О нас'),
7 | BotCommand(command='payment', description='Варианты оплаты'),
8 | BotCommand(command='shipping', description='Варианты доставки'),
9 | ]
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/common/bot_cmds_list.py:
--------------------------------------------------------------------------------
1 | # from aiogram.types import BotCommand
2 |
3 |
4 | # private = [
5 | # BotCommand(command='menu', description='Посмотреть меню'),
6 | # BotCommand(command='about', description='О нас'),
7 | # BotCommand(command='payment', description='Варианты оплаты'),
8 | # BotCommand(command='shipping', description='Варианты доставки'),
9 | # ]
--------------------------------------------------------------------------------
/lesson_8 template/kbds/inline.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import InlineKeyboardButton
2 | from aiogram.utils.keyboard import InlineKeyboardBuilder
3 |
4 |
5 |
6 | def get_callback_btns(*, btns: dict[str, str], sizes: tuple[int] = (2,)):
7 | keyboard = InlineKeyboardBuilder()
8 |
9 | for text, data in btns.items():
10 | keyboard.add(InlineKeyboardButton(text=text, callback_data=data))
11 |
12 | return keyboard.adjust(*sizes).as_markup()
13 |
--------------------------------------------------------------------------------
/lesson_3/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import types, Router
2 | from aiogram.filters import CommandStart, Command
3 |
4 | user_private_router = Router()
5 |
6 |
7 | @user_private_router.message(CommandStart())
8 | async def start_cmd(message: types.Message):
9 | await message.answer("Привет, я виртуальный помощник")
10 |
11 |
12 | @user_private_router.message(Command('menu', 'name'))
13 | async def menu_cmd(message: types.Message):
14 | await message.answer("Вот меню:")
15 |
--------------------------------------------------------------------------------
/lesson_7/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import Bot, types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
11 |
12 |
13 | class IsAdmin(Filter):
14 | def __init__(self) -> None:
15 | pass
16 |
17 | async def __call__(self, message: types.Message, bot: Bot) -> bool:
18 | return message.from_user.id in bot.my_admins_list
--------------------------------------------------------------------------------
/lesson_6-FSM/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import Bot, types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
11 |
12 |
13 | class IsAdmin(Filter):
14 | def __init__(self) -> None:
15 | pass
16 |
17 | async def __call__(self, message: types.Message, bot: Bot) -> bool:
18 | return message.from_user.id in bot.my_admins_list
--------------------------------------------------------------------------------
/lesson_8 template/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import Bot, types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
11 |
12 |
13 | class IsAdmin(Filter):
14 | def __init__(self) -> None:
15 | pass
16 |
17 | async def __call__(self, message: types.Message, bot: Bot) -> bool:
18 | return message.from_user.id in bot.my_admins_list
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import Bot, types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
11 |
12 |
13 | class IsAdmin(Filter):
14 | def __init__(self) -> None:
15 | pass
16 |
17 | async def __call__(self, message: types.Message, bot: Bot) -> bool:
18 | return message.from_user.id in bot.my_admins_list
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/filters/chat_types.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters import Filter
2 | from aiogram import Bot, types
3 |
4 |
5 | class ChatTypeFilter(Filter):
6 | def __init__(self, chat_types: list[str]) -> None:
7 | self.chat_types = chat_types
8 |
9 | async def __call__(self, message: types.Message) -> bool:
10 | return message.chat.type in self.chat_types
11 |
12 |
13 | class IsAdmin(Filter):
14 | def __init__(self) -> None:
15 | pass
16 |
17 | async def __call__(self, message: types.Message, bot: Bot) -> bool:
18 | return message.from_user.id in bot.my_admins_list
--------------------------------------------------------------------------------
/lesson_2/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from aiogram import Bot, Dispatcher, types
3 | from aiogram.filters import CommandStart
4 |
5 | bot = Bot(token="your TOKEN here")
6 | dp = Dispatcher()
7 |
8 |
9 | @dp.message(CommandStart())
10 | async def start_cmd(message: types.Message):
11 | await message.answer('Это была команда старт')
12 |
13 |
14 | @dp.message()
15 | async def echo(message: types.Message):
16 |
17 | await message.answer(message.text)
18 | await message.reply(message.text)
19 |
20 |
21 | async def main():
22 | await bot.delete_webhook(drop_pending_updates=True)
23 | await dp.start_polling(bot)
24 |
25 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_3/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.filters import CommandStart
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 | load_dotenv(find_dotenv())
9 |
10 | from handlers.user_private import user_private_router
11 |
12 |
13 | ALLOWED_UPDATES = ['message, edited_message']
14 |
15 | bot = Bot(token=os.getenv('TOKEN'))
16 | dp = Dispatcher()
17 |
18 | dp.include_router(user_private_router)
19 |
20 |
21 |
22 | async def main():
23 | await bot.delete_webhook(drop_pending_updates=True)
24 | await dp.start_polling(bot, allowed_updates=ALLOWED_UPDATES)
25 |
26 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_7/database/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import DateTime, Float, String, Text, func
2 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
3 |
4 |
5 | class Base(DeclarativeBase):
6 | created: Mapped[DateTime] = mapped_column(DateTime, default=func.now())
7 | updated: Mapped[DateTime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
8 |
9 |
10 | class Product(Base):
11 | __tablename__ = 'product'
12 |
13 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
14 | name: Mapped[str] = mapped_column(String(150), nullable=False)
15 | description: Mapped[str] = mapped_column(Text)
16 | price: Mapped[float] = mapped_column(Float(asdecimal=True), nullable=False)
17 | image: Mapped[str] = mapped_column(String(150))
18 |
--------------------------------------------------------------------------------
/lesson_1/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from aiogram import Bot, Dispatcher, types
3 | from aiogram.filters import CommandStart
4 |
5 | bot = Bot(token="your TOKEN here")
6 | dp = Dispatcher()
7 |
8 |
9 | @dp.message(CommandStart())
10 | async def start_cmd(message: types.Message):
11 | await message.answer('Это была команда старт')
12 |
13 | @dp.message()
14 | async def echo(message: types.Message):
15 | text = message.text
16 |
17 | if text in ['Привет', 'привет', 'hi', 'hello']:
18 | await message.answer('И тебе привет!')
19 | elif text in ['Пока', 'пока', 'До свидания']:
20 | await message.answer('И тебе пока!')
21 | else:
22 | await message.answer(message.text)
23 |
24 |
25 | async def main():
26 | await dp.start_polling(bot)
27 |
28 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_7/database/engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
3 |
4 | from database.models import Base
5 |
6 | #from .env file:
7 | # DB_LITE=sqlite+aiosqlite:///my_base.db
8 | # DB_URL=postgresql+asyncpg://login:password@localhost:5432/db_name
9 |
10 | # engine = create_async_engine(os.getenv('DB_LITE'), echo=True)
11 |
12 | engine = create_async_engine(os.getenv('DB_URL'), echo=True)
13 |
14 | session_maker = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
15 |
16 |
17 | async def create_db():
18 | async with engine.begin() as conn:
19 | await conn.run_sync(Base.metadata.create_all)
20 |
21 |
22 | async def drop_db():
23 | async with engine.begin() as conn:
24 | await conn.run_sync(Base.metadata.drop_all)
25 |
--------------------------------------------------------------------------------
/lesson_4/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, types, Router
4 |
5 | from filters.chat_types import ChatTypeFilter
6 |
7 | user_group_router = Router()
8 | user_group_router.message.filter(ChatTypeFilter(['group', 'supergroup']))
9 | user_group_router.edited_message.filter(ChatTypeFilter(['group', 'supergroup']))
10 |
11 |
12 | restricted_words = {'кабан', 'хомяк', 'выхухоль'}
13 |
14 | def clean_text(text: str):
15 | return text.translate(str.maketrans('', '', punctuation))
16 |
17 |
18 | @user_group_router.edited_message()
19 | @user_group_router.message()
20 | async def cleaner(message: types.Message):
21 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
22 | await message.answer(f"{message.from_user.first_name}, соблюддайте порядок в чате!")
23 | await message.delete()
24 | # await message.chat.ban(message.from_user.id)
--------------------------------------------------------------------------------
/lesson_5/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, types, Router
4 |
5 | from filters.chat_types import ChatTypeFilter
6 |
7 | user_group_router = Router()
8 | user_group_router.message.filter(ChatTypeFilter(['group', 'supergroup']))
9 | user_group_router.edited_message.filter(ChatTypeFilter(['group', 'supergroup']))
10 |
11 |
12 | restricted_words = {'кабан', 'хомяк', 'выхухоль'}
13 |
14 | def clean_text(text: str):
15 | return text.translate(str.maketrans('', '', punctuation))
16 |
17 |
18 | @user_group_router.edited_message()
19 | @user_group_router.message()
20 | async def cleaner(message: types.Message):
21 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
22 | await message.answer(f"{message.from_user.first_name}, соблюддайте порядок в чате!")
23 | await message.delete()
24 | # await message.chat.ban(message.from_user.id)
--------------------------------------------------------------------------------
/lesson_4/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 |
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 | load_dotenv(find_dotenv())
9 |
10 | from handlers.user_private import user_private_router
11 | from handlers.user_group import user_group_router
12 | from common.bot_cmds_list import private
13 |
14 | ALLOWED_UPDATES = ['message, edited_message']
15 |
16 | bot = Bot(token=os.getenv('TOKEN'))
17 | dp = Dispatcher()
18 |
19 | dp.include_router(user_private_router)
20 | dp.include_router(user_group_router)
21 |
22 |
23 |
24 | async def main():
25 | await bot.delete_webhook(drop_pending_updates=True)
26 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
27 | await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
28 | await dp.start_polling(bot, allowed_updates=ALLOWED_UPDATES)
29 |
30 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_5/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 | load_dotenv(find_dotenv())
9 |
10 | from handlers.user_private import user_private_router
11 | from handlers.user_group import user_group_router
12 |
13 | from common.bot_cmds_list import private
14 |
15 | ALLOWED_UPDATES = ['message, edited_message']
16 |
17 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
18 | dp = Dispatcher()
19 |
20 | dp.include_router(user_private_router)
21 | dp.include_router(user_group_router)
22 |
23 |
24 | async def main():
25 | await bot.delete_webhook(drop_pending_updates=True)
26 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
27 | await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
28 | await dp.start_polling(bot, allowed_updates=ALLOWED_UPDATES)
29 |
30 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_8 template/common/texts_for_db.py:
--------------------------------------------------------------------------------
1 | from aiogram.utils.formatting import Bold, as_list, as_marked_section
2 |
3 |
4 | categories = ['Еда', 'Напитки']
5 |
6 | description_for_info_pages = {
7 | "main": "Добро пожаловать!",
8 | "about": "Пиццерия Такая-то.\nРежим работы - круглосуточно.",
9 | "payment": as_marked_section(
10 | Bold("Варианты оплаты:"),
11 | "Картой в боте",
12 | "При получении карта/кеш",
13 | "В заведении",
14 | marker="✅ ",
15 | ).as_html(),
16 | "shipping": as_list(
17 | as_marked_section(
18 | Bold("Варианты доставки/заказа:"),
19 | "Курьер",
20 | "Самовынос (сейчас прибегу заберу)",
21 | "Покушаю у Вас (сейчас прибегу)",
22 | marker="✅ ",
23 | ),
24 | as_marked_section(Bold("Нельзя:"), "Почта", "Голуби", marker="❌ "),
25 | sep="\n----------------------\n",
26 | ).as_html(),
27 | 'catalog': 'Категории:',
28 | 'cart': 'В корзине ничего нет!'
29 | }
30 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/common/texts_for_db.py:
--------------------------------------------------------------------------------
1 | from aiogram.utils.formatting import Bold, as_list, as_marked_section
2 |
3 |
4 | categories = ['Еда', 'Напитки']
5 |
6 | description_for_info_pages = {
7 | "main": "Добро пожаловать!",
8 | "about": "Пиццерия Такая-то.\nРежим работы - круглосуточно.",
9 | "payment": as_marked_section(
10 | Bold("Варианты оплаты:"),
11 | "Картой в боте",
12 | "При получении карта/кеш",
13 | "В заведении",
14 | marker="✅ ",
15 | ).as_html(),
16 | "shipping": as_list(
17 | as_marked_section(
18 | Bold("Варианты доставки/заказа:"),
19 | "Курьер",
20 | "Самовынос (сейчас прибегу заберу)",
21 | "Покушаю у Вас (сейчас прибегу)",
22 | marker="✅ ",
23 | ),
24 | as_marked_section(Bold("Нельзя:"), "Почта", "Голуби", marker="❌ "),
25 | sep="\n----------------------\n",
26 | ).as_html(),
27 | 'catalog': 'Категории:',
28 | 'cart': 'В корзине ничего нет!'
29 | }
30 |
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 | load_dotenv(find_dotenv())
9 |
10 | from handlers.user_private import user_private_router
11 | from handlers.user_group import user_group_router
12 | from handlers.admin_private import admin_router
13 |
14 | from common.bot_cmds_list import private
15 |
16 | ALLOWED_UPDATES = ['message, edited_message']
17 |
18 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
19 | bot.my_admins_list = []
20 |
21 | dp = Dispatcher()
22 |
23 | dp.include_router(user_private_router)
24 | dp.include_router(user_group_router)
25 | dp.include_router(admin_router)
26 |
27 |
28 | async def main():
29 | await bot.delete_webhook(drop_pending_updates=True)
30 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
31 | await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
32 | await dp.start_polling(bot, allowed_updates=ALLOWED_UPDATES)
33 |
34 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_6-FSM/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 | from aiogram.fsm.strategy import FSMStrategy
7 |
8 | from dotenv import find_dotenv, load_dotenv
9 | load_dotenv(find_dotenv())
10 |
11 | from handlers.user_private import user_private_router
12 | from handlers.user_group import user_group_router
13 | from handlers.admin_private import admin_router
14 |
15 | from common.bot_cmds_list import private
16 |
17 | ALLOWED_UPDATES = ['message, edited_message']
18 |
19 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
20 | bot.my_admins_list = []
21 |
22 | dp = Dispatcher()
23 |
24 | dp.include_router(user_private_router)
25 | dp.include_router(user_group_router)
26 | dp.include_router(admin_router)
27 |
28 |
29 | async def main():
30 | await bot.delete_webhook(drop_pending_updates=True)
31 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
32 | await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
33 | await dp.start_polling(bot, allowed_updates=ALLOWED_UPDATES)
34 |
35 | asyncio.run(main())
--------------------------------------------------------------------------------
/lesson_8 template/database/engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
3 |
4 | from database.models import Base
5 | from database.orm_query import orm_add_banner_description, orm_create_categories
6 |
7 | from common.texts_for_db import categories, description_for_info_pages
8 |
9 | #from .env file:
10 | # DB_LITE=sqlite+aiosqlite:///my_base.db
11 | # DB_URL=postgresql+asyncpg://login:password@localhost:5432/db_name
12 |
13 | engine = create_async_engine(os.getenv('DB_LITE'), echo=True)
14 |
15 | # engine = create_async_engine(os.getenv('DB_URL'), echo=True)
16 |
17 | session_maker = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
18 |
19 |
20 |
21 | async def create_db():
22 | async with engine.begin() as conn:
23 | await conn.run_sync(Base.metadata.create_all)
24 |
25 | async with session_maker() as session:
26 | await orm_create_categories(session, categories)
27 | await orm_add_banner_description(session, description_for_info_pages)
28 |
29 |
30 | async def drop_db():
31 | async with engine.begin() as conn:
32 | await conn.run_sync(Base.metadata.drop_all)
33 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/database/engine.py:
--------------------------------------------------------------------------------
1 | import os
2 | from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
3 |
4 | from database.models import Base
5 | from database.orm_query import orm_add_banner_description, orm_create_categories
6 |
7 | from common.texts_for_db import categories, description_for_info_pages
8 |
9 | #from .env file:
10 | # DB_LITE=sqlite+aiosqlite:///my_base.db
11 | # DB_URL=postgresql+asyncpg://login:password@localhost:5432/db_name
12 |
13 | # engine = create_async_engine(os.getenv('DB_LITE'), echo=True)
14 |
15 | engine = create_async_engine(os.getenv('DB_URL'), echo=True)
16 |
17 | session_maker = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
18 |
19 |
20 |
21 | async def create_db():
22 | async with engine.begin() as conn:
23 | await conn.run_sync(Base.metadata.create_all)
24 |
25 | async with session_maker() as session:
26 | await orm_create_categories(session, categories)
27 | await orm_add_banner_description(session, description_for_info_pages)
28 |
29 |
30 | async def drop_db():
31 | async with engine.begin() as conn:
32 | await conn.run_sync(Base.metadata.drop_all)
33 |
--------------------------------------------------------------------------------
/lesson_7/middlewares/db.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Awaitable, Callable, Dict
2 |
3 | from aiogram import BaseMiddleware
4 | from aiogram.types import Message, TelegramObject
5 |
6 | from sqlalchemy.ext.asyncio import async_sessionmaker
7 |
8 |
9 | class DataBaseSession(BaseMiddleware):
10 | def __init__(self, session_pool: async_sessionmaker):
11 | self.session_pool = session_pool
12 |
13 |
14 | async def __call__(
15 | self,
16 | handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
17 | event: TelegramObject,
18 | data: Dict[str, Any],
19 | ) -> Any:
20 | async with self.session_pool() as session:
21 | data['session'] = session
22 | return await handler(event, data)
23 |
24 |
25 | # class CounterMiddleware(BaseMiddleware):
26 | # def __init__(self) -> None:
27 | # self.counter = 0
28 |
29 | # async def __call__(
30 | # self,
31 | # handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
32 | # event: TelegramObject,
33 | # data: Dict[str, Any]
34 | # ) -> Any:
35 | # self.counter += 1
36 | # data['counter'] = self.counter
37 | # return await handler(event, data)
--------------------------------------------------------------------------------
/lesson_8 template/middlewares/db.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Awaitable, Callable, Dict
2 |
3 | from aiogram import BaseMiddleware
4 | from aiogram.types import Message, TelegramObject
5 |
6 | from sqlalchemy.ext.asyncio import async_sessionmaker
7 |
8 |
9 | class DataBaseSession(BaseMiddleware):
10 | def __init__(self, session_pool: async_sessionmaker):
11 | self.session_pool = session_pool
12 |
13 |
14 | async def __call__(
15 | self,
16 | handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
17 | event: TelegramObject,
18 | data: Dict[str, Any],
19 | ) -> Any:
20 | async with self.session_pool() as session:
21 | data['session'] = session
22 | return await handler(event, data)
23 |
24 |
25 | # class CounterMiddleware(BaseMiddleware):
26 | # def __init__(self) -> None:
27 | # self.counter = 0
28 |
29 | # async def __call__(
30 | # self,
31 | # handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
32 | # event: TelegramObject,
33 | # data: Dict[str, Any]
34 | # ) -> Any:
35 | # self.counter += 1
36 | # data['counter'] = self.counter
37 | # return await handler(event, data)
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/middlewares/db.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Awaitable, Callable, Dict
2 |
3 | from aiogram import BaseMiddleware
4 | from aiogram.types import Message, TelegramObject
5 |
6 | from sqlalchemy.ext.asyncio import async_sessionmaker
7 |
8 |
9 | class DataBaseSession(BaseMiddleware):
10 | def __init__(self, session_pool: async_sessionmaker):
11 | self.session_pool = session_pool
12 |
13 |
14 | async def __call__(
15 | self,
16 | handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
17 | event: TelegramObject,
18 | data: Dict[str, Any],
19 | ) -> Any:
20 | async with self.session_pool() as session:
21 | data['session'] = session
22 | return await handler(event, data)
23 |
24 |
25 | # class CounterMiddleware(BaseMiddleware):
26 | # def __init__(self) -> None:
27 | # self.counter = 0
28 |
29 | # async def __call__(
30 | # self,
31 | # handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
32 | # event: TelegramObject,
33 | # data: Dict[str, Any]
34 | # ) -> Any:
35 | # self.counter += 1
36 | # data['counter'] = self.counter
37 | # return await handler(event, data)
--------------------------------------------------------------------------------
/lesson_7/kbds/inline.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import InlineKeyboardButton
2 | from aiogram.utils.keyboard import InlineKeyboardBuilder
3 |
4 |
5 | def get_callback_btns(
6 | *,
7 | btns: dict[str, str],
8 | sizes: tuple[int] = (2,)):
9 |
10 | keyboard = InlineKeyboardBuilder()
11 |
12 | for text, data in btns.items():
13 |
14 | keyboard.add(InlineKeyboardButton(text=text, callback_data=data))
15 |
16 | return keyboard.adjust(*sizes).as_markup()
17 |
18 |
19 | def get_url_btns(
20 | *,
21 | btns: dict[str, str],
22 | sizes: tuple[int] = (2,)):
23 |
24 | keyboard = InlineKeyboardBuilder()
25 |
26 | for text, url in btns.items():
27 |
28 | keyboard.add(InlineKeyboardButton(text=text, url=url))
29 |
30 | return keyboard.adjust(*sizes).as_markup()
31 |
32 |
33 | #Создать микс из CallBack и URL кнопок
34 | def get_inlineMix_btns(
35 | *,
36 | btns: dict[str, str],
37 | sizes: tuple[int] = (2,)):
38 |
39 | keyboard = InlineKeyboardBuilder()
40 |
41 | for text, value in btns.items():
42 | if '://' in value:
43 | keyboard.add(InlineKeyboardButton(text=text, url=value))
44 | else:
45 | keyboard.add(InlineKeyboardButton(text=text, callback_data=value))
46 |
47 | return keyboard.adjust(*sizes).as_markup()
--------------------------------------------------------------------------------
/lesson_8 template/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.filters import CommandStart
3 |
4 | from sqlalchemy.ext.asyncio import AsyncSession
5 | from database.orm_query import (
6 | orm_add_to_cart,
7 | orm_add_user,
8 | )
9 |
10 | from filters.chat_types import ChatTypeFilter
11 | from kbds.inline import get_callback_btns
12 |
13 |
14 |
15 | user_private_router = Router()
16 | user_private_router.message.filter(ChatTypeFilter(["private"]))
17 |
18 |
19 | @user_private_router.message(CommandStart())
20 | async def start_cmd(message: types.Message):
21 | await message.answer("Привет, я виртуальный помощник",
22 | reply_markup=get_callback_btns(btns={
23 | 'Нажми меня': 'some_1'
24 | }))
25 |
26 |
27 | @user_private_router.callback_query(F.data.startswith('some_'))
28 | async def counter(callback: types.CallbackQuery):
29 | number = int(callback.data.split('_')[-1])
30 |
31 | await callback.message.edit_text(
32 | text=f"Нажатий - {number}",
33 | reply_markup=get_callback_btns(btns={
34 | 'Нажми еще раз': f'some_{number+1}'
35 | }))
36 |
37 |
38 | # Пример для видео как делать не нужно:
39 | # menu_level_menuName_category_page_productID
--------------------------------------------------------------------------------
/lesson_7/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButton
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | def get_keyboard(
6 | *btns: str,
7 | placeholder: str = None,
8 | request_contact: int = None,
9 | request_location: int = None,
10 | sizes: tuple[int] = (2,),
11 | ):
12 | '''
13 | Parameters request_contact and request_location must be as indexes of btns args for buttons you need.
14 | Example:
15 | get_keyboard(
16 | "Меню",
17 | "О магазине",
18 | "Варианты оплаты",
19 | "Варианты доставки",
20 | "Отправить номер телефона",
21 | placeholder="Что вас интересует?",
22 | request_contact=4,
23 | sizes=(2, 2, 1)
24 | )
25 | '''
26 | keyboard = ReplyKeyboardBuilder()
27 |
28 | for index, text in enumerate(btns, start=0):
29 |
30 | if request_contact and request_contact == index:
31 | keyboard.add(KeyboardButton(text=text, request_contact=True))
32 |
33 | elif request_location and request_location == index:
34 | keyboard.add(KeyboardButton(text=text, request_location=True))
35 | else:
36 | keyboard.add(KeyboardButton(text=text))
37 |
38 | return keyboard.adjust(*sizes).as_markup(
39 | resize_keyboard=True, input_field_placeholder=placeholder)
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lesson_6-FSM/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButton
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | def get_keyboard(
6 | *btns: str,
7 | placeholder: str = None,
8 | request_contact: int = None,
9 | request_location: int = None,
10 | sizes: tuple[int] = (2,),
11 | ):
12 | '''
13 | Parameters request_contact and request_location must be as indexes of btns args for buttons you need.
14 | Example:
15 | get_keyboard(
16 | "Меню",
17 | "О магазине",
18 | "Варианты оплаты",
19 | "Варианты доставки",
20 | "Отправить номер телефона",
21 | placeholder="Что вас интересует?",
22 | request_contact=4,
23 | sizes=(2, 2, 1)
24 | )
25 | '''
26 | keyboard = ReplyKeyboardBuilder()
27 |
28 | for index, text in enumerate(btns, start=0):
29 |
30 | if request_contact and request_contact == index:
31 | keyboard.add(KeyboardButton(text=text, request_contact=True))
32 |
33 | elif request_location and request_location == index:
34 | keyboard.add(KeyboardButton(text=text, request_location=True))
35 | else:
36 | keyboard.add(KeyboardButton(text=text))
37 |
38 | return keyboard.adjust(*sizes).as_markup(
39 | resize_keyboard=True, input_field_placeholder=placeholder)
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lesson_8 template/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButton
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | def get_keyboard(
6 | *btns: str,
7 | placeholder: str = None,
8 | request_contact: int = None,
9 | request_location: int = None,
10 | sizes: tuple[int] = (2,),
11 | ):
12 | '''
13 | Parameters request_contact and request_location must be as indexes of btns args for buttons you need.
14 | Example:
15 | get_keyboard(
16 | "Меню",
17 | "О магазине",
18 | "Варианты оплаты",
19 | "Варианты доставки",
20 | "Отправить номер телефона",
21 | placeholder="Что вас интересует?",
22 | request_contact=4,
23 | sizes=(2, 2, 1)
24 | )
25 | '''
26 | keyboard = ReplyKeyboardBuilder()
27 |
28 | for index, text in enumerate(btns, start=0):
29 |
30 | if request_contact and request_contact == index:
31 | keyboard.add(KeyboardButton(text=text, request_contact=True))
32 |
33 | elif request_location and request_location == index:
34 | keyboard.add(KeyboardButton(text=text, request_location=True))
35 | else:
36 | keyboard.add(KeyboardButton(text=text))
37 |
38 | return keyboard.adjust(*sizes).as_markup(
39 | resize_keyboard=True, input_field_placeholder=placeholder)
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lesson_4/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.filters import CommandStart, Command, or_f
3 | from filters.chat_types import ChatTypeFilter
4 |
5 | user_private_router = Router()
6 | user_private_router.message.filter(ChatTypeFilter(['private']))
7 |
8 | @user_private_router.message(CommandStart())
9 | async def start_cmd(message: types.Message):
10 | await message.answer("Привет, я виртуальный помощник")
11 |
12 |
13 | # @user_private_router.message(F.text.lower() == "меню")
14 | @user_private_router.message(or_f(Command("menu"), (F.text.lower() == "меню")))
15 | async def menu_cmd(message: types.Message):
16 | await message.answer("Вот меню:")
17 |
18 |
19 | @user_private_router.message(F.text.lower() == "о нас")
20 | @user_private_router.message(Command("about"))
21 | async def about_cmd(message: types.Message):
22 | await message.answer("О нас:")
23 |
24 |
25 | @user_private_router.message(F.text.lower() == "варианты оплаты")
26 | @user_private_router.message(Command("payment"))
27 | async def payment_cmd(message: types.Message):
28 | await message.answer("Варианты оплаты:")
29 |
30 |
31 | @user_private_router.message((F.text.lower().contains('доставк')) | (F.text.lower() == 'варианты доставки'))
32 | @user_private_router.message(Command("shipping"))
33 | async def menu_cmd(message: types.Message):
34 | await message.answer("Варианты доставки:")
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButton
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | def get_keyboard(
6 | *btns: str,
7 | placeholder: str = None,
8 | request_contact: int = None,
9 | request_location: int = None,
10 | sizes: tuple[int] = (2,),
11 | ):
12 | '''
13 | Parameters request_contact and request_location must be as indexes of btns args for buttons you need.
14 | Example:
15 | get_keyboard(
16 | "Меню",
17 | "О магазине",
18 | "Варианты оплаты",
19 | "Варианты доставки",
20 | "Отправить номер телефона",
21 | placeholder="Что вас интересует?",
22 | request_contact=4,
23 | sizes=(2, 2, 1)
24 | )
25 | '''
26 | keyboard = ReplyKeyboardBuilder()
27 |
28 | for index, text in enumerate(btns, start=0):
29 |
30 | if request_contact and request_contact == index:
31 | keyboard.add(KeyboardButton(text=text, request_contact=True))
32 |
33 | elif request_location and request_location == index:
34 | keyboard.add(KeyboardButton(text=text, request_location=True))
35 | else:
36 | keyboard.add(KeyboardButton(text=text))
37 |
38 | return keyboard.adjust(*sizes).as_markup(
39 | resize_keyboard=True, input_field_placeholder=placeholder)
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButton
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | def get_keyboard(
6 | *btns: str,
7 | placeholder: str = None,
8 | request_contact: int = None,
9 | request_location: int = None,
10 | sizes: tuple[int] = (2,),
11 | ):
12 | '''
13 | Parameters request_contact and request_location must be as indexes of btns args for buttons you need.
14 | Example:
15 | get_keyboard(
16 | "Меню",
17 | "О магазине",
18 | "Варианты оплаты",
19 | "Варианты доставки",
20 | "Отправить номер телефона"
21 | placeholder="Что вас интересует?",
22 | request_contact=4,
23 | sizes=(2, 2, 1)
24 | )
25 | '''
26 | keyboard = ReplyKeyboardBuilder()
27 |
28 | for index, text in enumerate(btns, start=0):
29 |
30 | if request_contact and request_contact == index:
31 | keyboard.add(KeyboardButton(text=text, request_contact=True))
32 |
33 | elif request_location and request_location == index:
34 | keyboard.add(KeyboardButton(text=text, request_location=True))
35 | else:
36 |
37 | keyboard.add(KeyboardButton(text=text))
38 |
39 | return keyboard.adjust(*sizes).as_markup(
40 | resize_keyboard=True, input_field_placeholder=placeholder)
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/utils/paginator.py:
--------------------------------------------------------------------------------
1 | import math
2 |
3 |
4 | # Простой пагинатор
5 | class Paginator:
6 | def __init__(self, array: list | tuple, page: int=1, per_page: int=1):
7 | self.array = array
8 | self.per_page = per_page
9 | self.page = page
10 | self.len = len(self.array)
11 | # math.ceil - округление в большую сторону до целого числа
12 | self.pages = math.ceil(self.len / self.per_page)
13 |
14 | def __get_slice(self):
15 | start = (self.page - 1) * self.per_page
16 | stop = start + self.per_page
17 | return self.array[start:stop]
18 |
19 | def get_page(self):
20 | page_items = self.__get_slice()
21 | return page_items
22 |
23 | def has_next(self):
24 | if self.page < self.pages:
25 | return self.page + 1
26 | return False
27 |
28 | def has_previous(self):
29 | if self.page > 1:
30 | return self.page - 1
31 | return False
32 |
33 | def get_next(self):
34 | if self.page < self.pages:
35 | self.page += 1
36 | return self.get_page()
37 | raise IndexError(f'Next page does not exist. Use has_next() to check before.')
38 |
39 | def get_previous(self):
40 | if self.page > 1:
41 | self.page -= 1
42 | return self.__get_slice()
43 | raise IndexError(f'Previous page does not exist. Use has_previous() to check before.')
--------------------------------------------------------------------------------
/lesson_7/database/orm_query.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import select, update, delete
2 | from sqlalchemy.ext.asyncio import AsyncSession
3 |
4 | from database.models import Product
5 |
6 |
7 | async def orm_add_product(session: AsyncSession, data: dict):
8 | obj = Product(
9 | name=data["name"],
10 | description=data["description"],
11 | price=float(data["price"]),
12 | image=data["image"],
13 | )
14 | session.add(obj)
15 | await session.commit()
16 |
17 |
18 | async def orm_get_products(session: AsyncSession):
19 | query = select(Product)
20 | result = await session.execute(query)
21 | return result.scalars().all()
22 |
23 |
24 | async def orm_get_product(session: AsyncSession, product_id: int):
25 | query = select(Product).where(Product.id == product_id)
26 | result = await session.execute(query)
27 | return result.scalar()
28 |
29 |
30 | async def orm_update_product(session: AsyncSession, product_id: int, data):
31 | query = update(Product).where(Product.id == product_id).values(
32 | name=data["name"],
33 | description=data["description"],
34 | price=float(data["price"]),
35 | image=data["image"],)
36 | await session.execute(query)
37 | await session.commit()
38 |
39 |
40 | async def orm_delete_product(session: AsyncSession, product_id: int):
41 | query = delete(Product).where(Product.id == product_id)
42 | await session.execute(query)
43 | await session.commit()
44 |
--------------------------------------------------------------------------------
/lesson_5/kbds/reply.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import KeyboardButtonPollType, ReplyKeyboardMarkup, KeyboardButton, ReplyKeyboardRemove
2 | from aiogram.utils.keyboard import ReplyKeyboardBuilder
3 |
4 |
5 | start_kb = ReplyKeyboardMarkup(
6 | keyboard=[
7 | [
8 | KeyboardButton(text="Меню"),
9 | KeyboardButton(text="О магазине"),
10 | ],
11 | {
12 | KeyboardButton(text="Варианты доставки"),
13 | KeyboardButton(text="Варианты оплаты"),
14 | }
15 | ],
16 | resize_keyboard=True,
17 | input_field_placeholder='Что Вас интересует?'
18 | )
19 |
20 | del_kbd = ReplyKeyboardRemove()
21 |
22 |
23 | start_kb2 = ReplyKeyboardBuilder()
24 | start_kb2.add(
25 | KeyboardButton(text="Меню"),
26 | KeyboardButton(text="О магазине"),
27 | KeyboardButton(text="Варианты доставки"),
28 | KeyboardButton(text="Варианты оплаты"),
29 | )
30 | start_kb2.adjust(2, 2)
31 |
32 |
33 | start_kb3 = ReplyKeyboardBuilder()
34 | start_kb3.attach(start_kb2)
35 | start_kb3.row(KeyboardButton(text="Оставить отзыв"),)
36 |
37 |
38 | test_kb = ReplyKeyboardMarkup(
39 | keyboard=[
40 | [
41 | KeyboardButton(text="Создать опрос", request_poll=KeyboardButtonPollType()),
42 | ],
43 | [
44 | KeyboardButton(text="Отправить номер ☎️", request_contact=True),
45 | KeyboardButton(text="Отправить локацию 🗺️", request_location=True),
46 | ],
47 | ],
48 | resize_keyboard=True,
49 | )
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lesson_8 template/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 |
9 | load_dotenv(find_dotenv())
10 |
11 | from middlewares.db import DataBaseSession
12 |
13 | from database.engine import create_db, drop_db, session_maker
14 |
15 | from handlers.user_private import user_private_router
16 | from handlers.user_group import user_group_router
17 | from handlers.admin_private import admin_router
18 |
19 | # from common.bot_cmds_list import private
20 |
21 |
22 | # ALLOWED_UPDATES = ['message', 'edited_message', 'callback_query']
23 |
24 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
25 | bot.my_admins_list = []
26 |
27 | dp = Dispatcher()
28 |
29 | dp.include_router(user_private_router)
30 | dp.include_router(user_group_router)
31 | dp.include_router(admin_router)
32 |
33 |
34 | async def on_startup(bot):
35 |
36 | # await drop_db()
37 |
38 | await create_db()
39 |
40 |
41 | async def on_shutdown(bot):
42 | print('бот лег')
43 |
44 |
45 | async def main():
46 | dp.startup.register(on_startup)
47 | dp.shutdown.register(on_shutdown)
48 |
49 | dp.update.middleware(DataBaseSession(session_pool=session_maker))
50 |
51 | await bot.delete_webhook(drop_pending_updates=True)
52 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
53 | # await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
54 | await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
55 |
56 | asyncio.run(main())
57 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 |
9 | load_dotenv(find_dotenv())
10 |
11 | from middlewares.db import DataBaseSession
12 |
13 | from database.engine import create_db, drop_db, session_maker
14 |
15 | from handlers.user_private import user_private_router
16 | from handlers.user_group import user_group_router
17 | from handlers.admin_private import admin_router
18 |
19 | # from common.bot_cmds_list import private
20 |
21 |
22 | # ALLOWED_UPDATES = ['message', 'edited_message', 'callback_query']
23 |
24 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
25 | bot.my_admins_list = []
26 |
27 | dp = Dispatcher()
28 |
29 | dp.include_router(user_private_router)
30 | dp.include_router(user_group_router)
31 | dp.include_router(admin_router)
32 |
33 |
34 | async def on_startup(bot):
35 |
36 | # await drop_db()
37 |
38 | await create_db()
39 |
40 |
41 | async def on_shutdown(bot):
42 | print('бот лег')
43 |
44 |
45 | async def main():
46 | dp.startup.register(on_startup)
47 | dp.shutdown.register(on_shutdown)
48 |
49 | dp.update.middleware(DataBaseSession(session_pool=session_maker))
50 |
51 | await bot.delete_webhook(drop_pending_updates=True)
52 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
53 | # await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
54 | await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
55 |
56 | asyncio.run(main())
57 |
--------------------------------------------------------------------------------
/lesson_7/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | import os
3 |
4 | from aiogram import Bot, Dispatcher, types
5 | from aiogram.enums import ParseMode
6 |
7 | from dotenv import find_dotenv, load_dotenv
8 |
9 | load_dotenv(find_dotenv())
10 |
11 | from middlewares.db import DataBaseSession
12 |
13 | from database.engine import create_db, drop_db, session_maker
14 |
15 | from handlers.user_private import user_private_router
16 | from handlers.user_group import user_group_router
17 | from handlers.admin_private import admin_router
18 |
19 | from common.bot_cmds_list import private
20 |
21 |
22 | # ALLOWED_UPDATES = ['message', 'edited_message', 'callback_query']
23 |
24 | bot = Bot(token=os.getenv('TOKEN'), parse_mode=ParseMode.HTML)
25 | bot.my_admins_list = []
26 |
27 | dp = Dispatcher()
28 |
29 | dp.include_router(user_private_router)
30 | dp.include_router(user_group_router)
31 | dp.include_router(admin_router)
32 |
33 |
34 | async def on_startup(bot):
35 |
36 | run_param = False
37 | if run_param:
38 | await drop_db()
39 |
40 | await create_db()
41 |
42 |
43 | async def on_shutdown(bot):
44 | print('бот лег')
45 |
46 |
47 | async def main():
48 | dp.startup.register(on_startup)
49 | dp.shutdown.register(on_shutdown)
50 |
51 | dp.update.middleware(DataBaseSession(session_pool=session_maker))
52 |
53 | await bot.delete_webhook(drop_pending_updates=True)
54 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats())
55 | await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
56 | await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
57 |
58 | asyncio.run(main())
59 |
--------------------------------------------------------------------------------
/lesson_7/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, Bot, types, Router
4 | from aiogram.filters import Command
5 |
6 | from filters.chat_types import ChatTypeFilter
7 | from common.restricted_words import restricted_words
8 |
9 |
10 | user_group_router = Router()
11 | user_group_router.message.filter(ChatTypeFilter(["group", "supergroup"]))
12 | user_group_router.edited_message.filter(ChatTypeFilter(["group", "supergroup"]))
13 |
14 |
15 | @user_group_router.message(Command("admin"))
16 | async def get_admins(message: types.Message, bot: Bot):
17 | chat_id = message.chat.id
18 | admins_list = await bot.get_chat_administrators(chat_id)
19 | #просмотреть все данные и свойства полученных объектов
20 | #print(admins_list)
21 | # Код ниже это генератор списка, как и этот x = [i for i in range(10)]
22 | admins_list = [
23 | member.user.id
24 | for member in admins_list
25 | if member.status == "creator" or member.status == "administrator"
26 | ]
27 | bot.my_admins_list = admins_list
28 | if message.from_user.id in admins_list:
29 | await message.delete()
30 | #print(admins_list)
31 |
32 |
33 | def clean_text(text: str):
34 | return text.translate(str.maketrans("", "", punctuation))
35 |
36 |
37 | @user_group_router.edited_message()
38 | @user_group_router.message()
39 | async def cleaner(message: types.Message):
40 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
41 | await message.answer(
42 | f"{message.from_user.first_name}, соблюддайте порядок в чате!"
43 | )
44 | await message.delete()
45 | # await message.chat.ban(message.from_user.id)
46 |
--------------------------------------------------------------------------------
/lesson_6-FSM/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, Bot, types, Router
4 | from aiogram.filters import Command
5 |
6 | from filters.chat_types import ChatTypeFilter
7 | from common.restricted_words import restricted_words
8 |
9 |
10 | user_group_router = Router()
11 | user_group_router.message.filter(ChatTypeFilter(["group", "supergroup"]))
12 | user_group_router.edited_message.filter(ChatTypeFilter(["group", "supergroup"]))
13 |
14 |
15 | @user_group_router.message(Command("admin"))
16 | async def get_admins(message: types.Message, bot: Bot):
17 | chat_id = message.chat.id
18 | admins_list = await bot.get_chat_administrators(chat_id)
19 | #просмотреть все данные и свойства полученных объектов
20 | #print(admins_list)
21 | # Код ниже это генератор списка, как и этот x = [i for i in range(10)]
22 | admins_list = [
23 | member.user.id
24 | for member in admins_list
25 | if member.status == "creator" or member.status == "administrator"
26 | ]
27 | bot.my_admins_list = admins_list
28 | if message.from_user.id in admins_list:
29 | await message.delete()
30 | #print(admins_list)
31 |
32 |
33 | def clean_text(text: str):
34 | return text.translate(str.maketrans("", "", punctuation))
35 |
36 |
37 | @user_group_router.edited_message()
38 | @user_group_router.message()
39 | async def cleaner(message: types.Message):
40 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
41 | await message.answer(
42 | f"{message.from_user.first_name}, соблюддайте порядок в чате!"
43 | )
44 | await message.delete()
45 | # await message.chat.ban(message.from_user.id)
46 |
--------------------------------------------------------------------------------
/lesson_8 template/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, Bot, types, Router
4 | from aiogram.filters import Command
5 |
6 | from filters.chat_types import ChatTypeFilter
7 | from common.restricted_words import restricted_words
8 |
9 |
10 | user_group_router = Router()
11 | user_group_router.message.filter(ChatTypeFilter(["group", "supergroup"]))
12 | user_group_router.edited_message.filter(ChatTypeFilter(["group", "supergroup"]))
13 |
14 |
15 | @user_group_router.message(Command("admin"))
16 | async def get_admins(message: types.Message, bot: Bot):
17 | chat_id = message.chat.id
18 | admins_list = await bot.get_chat_administrators(chat_id)
19 | #просмотреть все данные и свойства полученных объектов
20 | #print(admins_list)
21 | # Код ниже это генератор списка, как и этот x = [i for i in range(10)]
22 | admins_list = [
23 | member.user.id
24 | for member in admins_list
25 | if member.status == "creator" or member.status == "administrator"
26 | ]
27 | bot.my_admins_list = admins_list
28 | if message.from_user.id in admins_list:
29 | await message.delete()
30 | #print(admins_list)
31 |
32 |
33 | def clean_text(text: str):
34 | return text.translate(str.maketrans("", "", punctuation))
35 |
36 |
37 | @user_group_router.edited_message()
38 | @user_group_router.message()
39 | async def cleaner(message: types.Message):
40 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
41 | await message.answer(
42 | f"{message.from_user.first_name}, соблюддайте порядок в чате!"
43 | )
44 | await message.delete()
45 | # await message.chat.ban(message.from_user.id)
46 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, Bot, types, Router
4 | from aiogram.filters import Command
5 |
6 | from filters.chat_types import ChatTypeFilter
7 | from common.restricted_words import restricted_words
8 |
9 |
10 | user_group_router = Router()
11 | user_group_router.message.filter(ChatTypeFilter(["group", "supergroup"]))
12 | user_group_router.edited_message.filter(ChatTypeFilter(["group", "supergroup"]))
13 |
14 |
15 | @user_group_router.message(Command("admin"))
16 | async def get_admins(message: types.Message, bot: Bot):
17 | chat_id = message.chat.id
18 | admins_list = await bot.get_chat_administrators(chat_id)
19 | #просмотреть все данные и свойства полученных объектов
20 | #print(admins_list)
21 | # Код ниже это генератор списка, как и этот x = [i for i in range(10)]
22 | admins_list = [
23 | member.user.id
24 | for member in admins_list
25 | if member.status == "creator" or member.status == "administrator"
26 | ]
27 | bot.my_admins_list = admins_list
28 | if message.from_user.id in admins_list:
29 | await message.delete()
30 | #print(admins_list)
31 |
32 |
33 | def clean_text(text: str):
34 | return text.translate(str.maketrans("", "", punctuation))
35 |
36 |
37 | @user_group_router.edited_message()
38 | @user_group_router.message()
39 | async def cleaner(message: types.Message):
40 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
41 | await message.answer(
42 | f"{message.from_user.first_name}, соблюддайте порядок в чате!"
43 | )
44 | await message.delete()
45 | # await message.chat.ban(message.from_user.id)
46 |
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/handlers/user_group.py:
--------------------------------------------------------------------------------
1 | from string import punctuation
2 |
3 | from aiogram import F, Bot, types, Router
4 | from aiogram.filters import Command
5 |
6 | from filters.chat_types import ChatTypeFilter
7 | from common.restricted_words import restricted_words
8 |
9 |
10 | user_group_router = Router()
11 | user_group_router.message.filter(ChatTypeFilter(["group", "supergroup"]))
12 | user_group_router.edited_message.filter(ChatTypeFilter(["group", "supergroup"]))
13 |
14 |
15 | @user_group_router.message(Command("admin"))
16 | async def get_admins(message: types.Message, bot: Bot):
17 | chat_id = message.chat.id
18 | admins_list = await bot.get_chat_administrators(chat_id)
19 | #просмотреть все данные и свойства полученных объектов
20 | #print(admins_list)
21 | # Код ниже это генератор списка, как и этот x = [i for i in range(10)]
22 | admins_list = [
23 | member.user.id
24 | for member in admins_list
25 | if member.status == "creator" or member.status == "administrator"
26 | ]
27 | bot.my_admins_list = admins_list
28 | if message.from_user.id in admins_list:
29 | await message.delete()
30 | #print(admins_list)
31 |
32 |
33 | def clean_text(text: str):
34 | return text.translate(str.maketrans("", "", punctuation))
35 |
36 |
37 | @user_group_router.edited_message()
38 | @user_group_router.message()
39 | async def cleaner(message: types.Message):
40 | if restricted_words.intersection(clean_text(message.text.lower()).split()):
41 | await message.answer(
42 | f"{message.from_user.first_name}, соблюддайте порядок в чате!"
43 | )
44 | await message.delete()
45 | # await message.chat.ban(message.from_user.id)
46 |
--------------------------------------------------------------------------------
/lesson_8 template/README.md:
--------------------------------------------------------------------------------
1 | # В видео №8 это расскажу обязательно, это легкая предподготовка, но вот изменения:
2 | В видео №8 будем работать с многоуровневым инлайн меню, каталогом, пагинацией и корзиной товаров пользователя.
3 |
4 | 1) Добавились новые таблицы БД, в том числе и категории товаров.
5 | 2) Подготовлены функции с нужными запросыми к ORM
6 | 3) В FSM админа и командах на просмотр/добавления/изменения соответственно тоже появился пункт категории
7 | 4) Исправил пару косяков в FSM по Вашим комментам
8 | 5) При старте бота автоматически наполняются таблицы: category и banner (бод баннером подразумевается пункт будущего инлайн меню не отображающий товары и прочее, а содержащий справочную информацию о about и тд...
9 | 6) Добавлена "микро" FSM для админа, для добавления изображения для отображения справочной информации о магазине, вроде about, варианты оплаты, доставки и тд.
10 | 7) Команды пользователя убраны, так как в дальнейшем не понадобятся. Чтоб убрать кнопку меню с ними в вашем боте - раскоментируйте на один запуск эту строчку в app.py -> main:
11 | ```
12 | async def main():
13 | dp.startup.register(on_startup)
14 | dp.shutdown.register(on_shutdown)
15 |
16 | dp.update.middleware(DataBaseSession(session_pool=session_maker))
17 |
18 | await bot.delete_webhook(drop_pending_updates=True)
19 | 👇👇👇Эту
20 | # await bot.delete_my_commands(scope=types.BotCommandScopeAllPrivateChats()) 👈<-------------ЭТУ
21 | # await bot.set_my_commands(commands=private, scope=types.BotCommandScopeAllPrivateChats())
22 | await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
23 | ```
24 | 8) Старая схема БД не подойдет для этого бота, поэтому при запуске этого бота в !первый раз - удалить старые таблицы (это уже предусмотрено, нужно раскоментировать на !первый запуск эту строчку кода в файле app.py:
25 |
26 | ```
27 | async def on_startup(bot):
28 | 👇👇👇Эту
29 | # await drop_db() 👈 <------ЭТУ
30 |
31 | await create_db()
32 | ```
33 | См. следующее видео - это все будет рассказано.
34 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.filters import CommandStart
3 |
4 | from sqlalchemy.ext.asyncio import AsyncSession
5 | from database.orm_query import (
6 | orm_add_to_cart,
7 | orm_add_user,
8 | )
9 |
10 | from filters.chat_types import ChatTypeFilter
11 | from handlers.menu_processing import get_menu_content
12 | from kbds.inline import MenuCallBack, get_callback_btns
13 |
14 |
15 |
16 | user_private_router = Router()
17 | user_private_router.message.filter(ChatTypeFilter(["private"]))
18 |
19 |
20 | @user_private_router.message(CommandStart())
21 | async def start_cmd(message: types.Message, session: AsyncSession):
22 | media, reply_markup = await get_menu_content(session, level=0, menu_name="main")
23 |
24 | await message.answer_photo(media.media, caption=media.caption, reply_markup=reply_markup)
25 |
26 |
27 | async def add_to_cart(callback: types.CallbackQuery, callback_data: MenuCallBack, session: AsyncSession):
28 | user = callback.from_user
29 | await orm_add_user(
30 | session,
31 | user_id=user.id,
32 | first_name=user.first_name,
33 | last_name=user.last_name,
34 | phone=None,
35 | )
36 | await orm_add_to_cart(session, user_id=user.id, product_id=callback_data.product_id)
37 | await callback.answer("Товар добавлен в корзину.")
38 |
39 |
40 | @user_private_router.callback_query(MenuCallBack.filter())
41 | async def user_menu(callback: types.CallbackQuery, callback_data: MenuCallBack, session: AsyncSession):
42 |
43 | if callback_data.menu_name == "add_to_cart":
44 | await add_to_cart(callback, callback_data, session)
45 | return
46 |
47 | media, reply_markup = await get_menu_content(
48 | session,
49 | level=callback_data.level,
50 | menu_name=callback_data.menu_name,
51 | category=callback_data.category,
52 | page=callback_data.page,
53 | product_id=callback_data.product_id,
54 | user_id=callback.from_user.id,
55 | )
56 |
57 | await callback.message.edit_media(media=media, reply_markup=reply_markup)
58 | await callback.answer()
--------------------------------------------------------------------------------
/lesson_8 template/database/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import DateTime, ForeignKey, Numeric, String, Text, BigInteger, func
2 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
3 |
4 |
5 | class Base(DeclarativeBase):
6 | created: Mapped[DateTime] = mapped_column(DateTime, default=func.now())
7 | updated: Mapped[DateTime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
8 |
9 |
10 | class Banner(Base):
11 | __tablename__ = 'banner'
12 |
13 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
14 | name: Mapped[str] = mapped_column(String(15), unique=True)
15 | image: Mapped[str] = mapped_column(String(150), nullable=True)
16 | description: Mapped[str] = mapped_column(Text, nullable=True)
17 |
18 |
19 | class Category(Base):
20 | __tablename__ = 'category'
21 |
22 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
23 | name: Mapped[str] = mapped_column(String(150), nullable=False)
24 |
25 |
26 | class Product(Base):
27 | __tablename__ = 'product'
28 |
29 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
30 | name: Mapped[str] = mapped_column(String(150), nullable=False)
31 | description: Mapped[str] = mapped_column(Text)
32 | price: Mapped[float] = mapped_column(Numeric(5,2), nullable=False)
33 | image: Mapped[str] = mapped_column(String(150))
34 | category_id: Mapped[int] = mapped_column(ForeignKey('category.id', ondelete='CASCADE'), nullable=False)
35 |
36 | category: Mapped['Category'] = relationship(backref='product')
37 |
38 |
39 | class User(Base):
40 | __tablename__ = 'user'
41 |
42 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
43 | user_id: Mapped[int] = mapped_column(BigInteger, unique=True)
44 | first_name: Mapped[str] = mapped_column(String(150), nullable=True)
45 | last_name: Mapped[str] = mapped_column(String(150), nullable=True)
46 | phone: Mapped[str] = mapped_column(String(13), nullable=True)
47 |
48 |
49 | class Cart(Base):
50 | __tablename__ = 'cart'
51 |
52 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
53 | user_id: Mapped[int] = mapped_column(ForeignKey('user.user_id', ondelete='CASCADE'), nullable=False)
54 | product_id: Mapped[int] = mapped_column(ForeignKey('product.id', ondelete='CASCADE'), nullable=False)
55 | quantity: Mapped[int]
56 |
57 | user: Mapped['User'] = relationship(backref='cart')
58 | product: Mapped['Product'] = relationship(backref='cart')
59 |
60 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/database/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy import DateTime, ForeignKey, Numeric, String, Text, BigInteger, func
2 | from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
3 |
4 |
5 | class Base(DeclarativeBase):
6 | created: Mapped[DateTime] = mapped_column(DateTime, default=func.now())
7 | updated: Mapped[DateTime] = mapped_column(DateTime, default=func.now(), onupdate=func.now())
8 |
9 |
10 | class Banner(Base):
11 | __tablename__ = 'banner'
12 |
13 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
14 | name: Mapped[str] = mapped_column(String(15), unique=True)
15 | image: Mapped[str] = mapped_column(String(150), nullable=True)
16 | description: Mapped[str] = mapped_column(Text, nullable=True)
17 |
18 |
19 | class Category(Base):
20 | __tablename__ = 'category'
21 |
22 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
23 | name: Mapped[str] = mapped_column(String(150), nullable=False)
24 |
25 |
26 | class Product(Base):
27 | __tablename__ = 'product'
28 |
29 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
30 | name: Mapped[str] = mapped_column(String(150), nullable=False)
31 | description: Mapped[str] = mapped_column(Text)
32 | price: Mapped[float] = mapped_column(Numeric(5,2), nullable=False)
33 | image: Mapped[str] = mapped_column(String(150))
34 | category_id: Mapped[int] = mapped_column(ForeignKey('category.id', ondelete='CASCADE'), nullable=False)
35 |
36 | category: Mapped['Category'] = relationship(backref='product')
37 |
38 |
39 | class User(Base):
40 | __tablename__ = 'user'
41 |
42 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
43 | user_id: Mapped[int] = mapped_column(BigInteger, unique=True)
44 | first_name: Mapped[str] = mapped_column(String(150), nullable=True)
45 | last_name: Mapped[str] = mapped_column(String(150), nullable=True)
46 | phone: Mapped[str] = mapped_column(String(13), nullable=True)
47 |
48 |
49 | class Cart(Base):
50 | __tablename__ = 'cart'
51 |
52 | id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
53 | user_id: Mapped[int] = mapped_column(ForeignKey('user.user_id', ondelete='CASCADE'), nullable=False)
54 | product_id: Mapped[int] = mapped_column(ForeignKey('product.id', ondelete='CASCADE'), nullable=False)
55 | quantity: Mapped[int]
56 |
57 | user: Mapped['User'] = relationship(backref='cart')
58 | product: Mapped['Product'] = relationship(backref='cart')
59 |
60 |
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, Router, types
2 | from aiogram.filters import Command
3 |
4 | from filters.chat_types import ChatTypeFilter, IsAdmin
5 | from kbds.reply import get_keyboard
6 |
7 |
8 | admin_router = Router()
9 | admin_router.message.filter(ChatTypeFilter(["private"]), IsAdmin())
10 |
11 |
12 | ADMIN_KB = get_keyboard(
13 | "Добавить товар",
14 | "Изменить товар",
15 | "Удалить товар",
16 | "Я так, просто посмотреть зашел",
17 | placeholder="Выберите действие",
18 | sizes=(2, 1, 1),
19 | )
20 |
21 |
22 | @admin_router.message(Command("admin"))
23 | async def add_product(message: types.Message):
24 | await message.answer("Что хотите сделать?", reply_markup=ADMIN_KB)
25 |
26 |
27 | @admin_router.message(F.text == "Я так, просто посмотреть зашел")
28 | async def starring_at_product(message: types.Message):
29 | await message.answer("ОК, вот список товаров")
30 |
31 |
32 | @admin_router.message(F.text == "Изменить товар")
33 | async def change_product(message: types.Message):
34 | await message.answer("ОК, вот список товаров")
35 |
36 |
37 | @admin_router.message(F.text == "Удалить товар")
38 | async def delete_product(message: types.Message):
39 | await message.answer("Выберите товар(ы) для удаления")
40 |
41 |
42 | #Код ниже для машины состояний (FSM)
43 |
44 | @admin_router.message(F.text == "Добавить товар")
45 | async def add_product(message: types.Message):
46 | await message.answer(
47 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
48 | )
49 |
50 |
51 | @admin_router.message(Command("отмена"))
52 | @admin_router.message(F.text.casefold() == "отмена")
53 | async def cancel_handler(message: types.Message) -> None:
54 | await message.answer("Действия отменены", reply_markup=ADMIN_KB)
55 |
56 |
57 | @admin_router.message(Command("назад"))
58 | @admin_router.message(F.text.casefold() == "назад")
59 | async def cancel_handler(message: types.Message) -> None:
60 | await message.answer(f"ок, вы вернулись к прошлому шагу")
61 |
62 |
63 | @admin_router.message(F.text)
64 | async def add_name(message: types.Message):
65 | await message.answer("Введите описание товара")
66 |
67 |
68 | @admin_router.message(F.text)
69 | async def add_description(message: types.Message):
70 | await message.answer("Введите стоимость товара")
71 |
72 |
73 | @admin_router.message(F.text)
74 | async def add_price(message: types.Message):
75 | await message.answer("Загрузите изображение товара")
76 |
77 |
78 | @admin_router.message(F.photo)
79 | async def add_image(message: types.Message):
80 | await message.answer("Товар добавлен", reply_markup=ADMIN_KB)
81 |
--------------------------------------------------------------------------------
/lesson_5/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.enums import ParseMode
3 | from aiogram.filters import CommandStart, Command, or_f
4 | from aiogram.utils.formatting import as_list, as_marked_section, Bold #Italic, as_numbered_list и тд
5 |
6 | from filters.chat_types import ChatTypeFilter
7 |
8 | from kbds import reply
9 |
10 | user_private_router = Router()
11 | user_private_router.message.filter(ChatTypeFilter(["private"]))
12 |
13 |
14 | @user_private_router.message(CommandStart())
15 | async def start_cmd(message: types.Message):
16 | await message.answer("Привет, я виртуальный помощник",
17 | reply_markup=reply.start_kb3.as_markup(
18 | resize_keyboard=True,
19 | input_field_placeholder='Что Вас интересует?'))
20 |
21 |
22 | # @user_private_router.message(F.text.lower() == "меню")
23 | @user_private_router.message(or_f(Command("menu"), (F.text.lower() == "меню")))
24 | async def menu_cmd(message: types.Message):
25 | await message.answer("Вот меню:")
26 |
27 |
28 | @user_private_router.message(F.text.lower() == "о магазине")
29 | @user_private_router.message(Command("about"))
30 | async def about_cmd(message: types.Message):
31 | await message.answer("О нас:")
32 |
33 |
34 | @user_private_router.message(F.text.lower() == "варианты оплаты")
35 | @user_private_router.message(Command("payment"))
36 | async def payment_cmd(message: types.Message):
37 |
38 | text = as_marked_section(
39 | Bold("Варианты оплаты:"),
40 | "Картой в боте",
41 | "При получении карта/кеш",
42 | "В заведении",
43 | marker='✅ '
44 | )
45 | await message.answer(text.as_html())
46 |
47 |
48 | @user_private_router.message(
49 | (F.text.lower().contains("доставк")) | (F.text.lower() == "варианты доставки")
50 | )
51 | @user_private_router.message(Command("shipping"))
52 | async def menu_cmd(message: types.Message):
53 | text = as_list(
54 | as_marked_section(
55 | Bold("Варианты доставки/заказа:"),
56 | "Курьер",
57 | "Самовынос (сейчас прибегу заберу)",
58 | "Покушаю у Вас (сейчас прибегу)",
59 | marker='✅ '
60 | ),
61 | as_marked_section(
62 | Bold("Нельзя:"),
63 | "Почта",
64 | "Голуби",
65 | marker='❌ '
66 | ),
67 | sep='\n----------------------\n'
68 | )
69 | await message.answer(text.as_html())
70 |
71 |
72 | @user_private_router.message(F.contact)
73 | async def get_contact(message: types.Message):
74 | await message.answer(f"номер получен")
75 | await message.answer(str(message.contact))
76 |
77 |
78 | @user_private_router.message(F.location)
79 | async def get_location(message: types.Message):
80 | await message.answer(f"локация получена")
81 | await message.answer(str(message.location))
--------------------------------------------------------------------------------
/lesson_6-FSM/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.enums import ParseMode
3 | from aiogram.filters import CommandStart, Command, or_f
4 | from aiogram.utils.formatting import (
5 | as_list,
6 | as_marked_section,
7 | Bold,
8 | ) # Italic, as_numbered_list и тд
9 |
10 | from filters.chat_types import ChatTypeFilter
11 |
12 | from kbds.reply import get_keyboard
13 |
14 | user_private_router = Router()
15 | user_private_router.message.filter(ChatTypeFilter(["private"]))
16 |
17 |
18 | @user_private_router.message(CommandStart())
19 | async def start_cmd(message: types.Message):
20 | await message.answer(
21 | "Привет, я виртуальный помощник",
22 | reply_markup=get_keyboard(
23 | "Меню",
24 | "О магазине",
25 | "Варианты оплаты",
26 | "Варианты доставки",
27 | placeholder="Что вас интересует?",
28 | sizes=(2, 2)
29 | ),
30 | )
31 |
32 |
33 | # @user_private_router.message(F.text.lower() == "меню")
34 | @user_private_router.message(or_f(Command("menu"), (F.text.lower() == "меню")))
35 | async def menu_cmd(message: types.Message):
36 | await message.answer("Вот меню:")
37 |
38 |
39 | @user_private_router.message(F.text.lower() == "о магазине")
40 | @user_private_router.message(Command("about"))
41 | async def about_cmd(message: types.Message):
42 | await message.answer("О нас:")
43 |
44 |
45 | @user_private_router.message(F.text.lower() == "варианты оплаты")
46 | @user_private_router.message(Command("payment"))
47 | async def payment_cmd(message: types.Message):
48 | text = as_marked_section(
49 | Bold("Варианты оплаты:"),
50 | "Картой в боте",
51 | "При получении карта/кеш",
52 | "В заведении",
53 | marker="✅ ",
54 | )
55 | await message.answer(text.as_html())
56 |
57 |
58 | @user_private_router.message(
59 | (F.text.lower().contains("доставк")) | (F.text.lower() == "варианты доставки"))
60 | @user_private_router.message(Command("shipping"))
61 | async def menu_cmd(message: types.Message):
62 | text = as_list(
63 | as_marked_section(
64 | Bold("Варианты доставки/заказа:"),
65 | "Курьер",
66 | "Самовынос (сейчас прибегу заберу)",
67 | "Покушаю у Вас (сейчас прибегу)",
68 | marker="✅ ",
69 | ),
70 | as_marked_section(
71 | Bold("Нельзя:"),
72 | "Почта",
73 | "Голуби",
74 | marker="❌ "
75 | ),
76 | sep="\n----------------------\n",
77 | )
78 | await message.answer(text.as_html())
79 |
80 |
81 | # @user_private_router.message(F.contact)
82 | # async def get_contact(message: types.Message):
83 | # await message.answer(f"номер получен")
84 | # await message.answer(str(message.contact))
85 |
86 |
87 | # @user_private_router.message(F.location)
88 | # async def get_location(message: types.Message):
89 | # await message.answer(f"локация получена")
90 | # await message.answer(str(message.location))
91 |
--------------------------------------------------------------------------------
/lesson_6 -Prepared_template_for_review/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.enums import ParseMode
3 | from aiogram.filters import CommandStart, Command, or_f
4 | from aiogram.utils.formatting import (
5 | as_list,
6 | as_marked_section,
7 | Bold,
8 | ) # Italic, as_numbered_list и тд
9 |
10 | from filters.chat_types import ChatTypeFilter
11 |
12 | from kbds.reply import get_keyboard
13 |
14 | user_private_router = Router()
15 | user_private_router.message.filter(ChatTypeFilter(["private"]))
16 |
17 |
18 | @user_private_router.message(CommandStart())
19 | async def start_cmd(message: types.Message):
20 | await message.answer(
21 | "Привет, я виртуальный помощник",
22 | reply_markup=get_keyboard(
23 | "Меню",
24 | "О магазине",
25 | "Варианты оплаты",
26 | "Варианты доставки",
27 | placeholder="Что вас интересует?",
28 | sizes=(2, 2)
29 | ),
30 | )
31 |
32 |
33 | # @user_private_router.message(F.text.lower() == "меню")
34 | @user_private_router.message(or_f(Command("menu"), (F.text.lower() == "меню")))
35 | async def menu_cmd(message: types.Message):
36 | await message.answer("Вот меню:")
37 |
38 |
39 | @user_private_router.message(F.text.lower() == "о магазине")
40 | @user_private_router.message(Command("about"))
41 | async def about_cmd(message: types.Message):
42 | await message.answer("О нас:")
43 |
44 |
45 | @user_private_router.message(F.text.lower() == "варианты оплаты")
46 | @user_private_router.message(Command("payment"))
47 | async def payment_cmd(message: types.Message):
48 | text = as_marked_section(
49 | Bold("Варианты оплаты:"),
50 | "Картой в боте",
51 | "При получении карта/кеш",
52 | "В заведении",
53 | marker="✅ ",
54 | )
55 | await message.answer(text.as_html())
56 |
57 |
58 | @user_private_router.message(
59 | (F.text.lower().contains("доставк")) | (F.text.lower() == "варианты доставки"))
60 | @user_private_router.message(Command("shipping"))
61 | async def menu_cmd(message: types.Message):
62 | text = as_list(
63 | as_marked_section(
64 | Bold("Варианты доставки/заказа:"),
65 | "Курьер",
66 | "Самовынос (сейчас прибегу заберу)",
67 | "Покушаю у Вас (сейчас прибегу)",
68 | marker="✅ ",
69 | ),
70 | as_marked_section(
71 | Bold("Нельзя:"),
72 | "Почта",
73 | "Голуби",
74 | marker="❌ "
75 | ),
76 | sep="\n----------------------\n",
77 | )
78 | await message.answer(text.as_html())
79 |
80 |
81 | # @user_private_router.message(F.contact)
82 | # async def get_contact(message: types.Message):
83 | # await message.answer(f"номер получен")
84 | # await message.answer(str(message.contact))
85 |
86 |
87 | # @user_private_router.message(F.location)
88 | # async def get_location(message: types.Message):
89 | # await message.answer(f"локация получена")
90 | # await message.answer(str(message.location))
91 |
--------------------------------------------------------------------------------
/lesson_7/handlers/user_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, types, Router
2 | from aiogram.enums import ParseMode
3 | from aiogram.filters import CommandStart, Command, or_f
4 | from aiogram.utils.formatting import (
5 | as_list,
6 | as_marked_section,
7 | Bold,
8 | )
9 | from sqlalchemy.ext.asyncio import AsyncSession
10 | from database.orm_query import orm_get_products # Italic, as_numbered_list и тд
11 |
12 | from filters.chat_types import ChatTypeFilter
13 |
14 | from kbds.reply import get_keyboard
15 |
16 | user_private_router = Router()
17 | user_private_router.message.filter(ChatTypeFilter(["private"]))
18 |
19 |
20 | @user_private_router.message(CommandStart())
21 | async def start_cmd(message: types.Message):
22 | await message.answer(
23 | "Привет, я виртуальный помощник",
24 | reply_markup=get_keyboard(
25 | "Меню",
26 | "О магазине",
27 | "Варианты оплаты",
28 | "Варианты доставки",
29 | placeholder="Что вас интересует?",
30 | sizes=(2, 2)
31 | ),
32 | )
33 |
34 |
35 | # @user_private_router.message(F.text.lower() == "меню")
36 | @user_private_router.message(or_f(Command("menu"), (F.text.lower() == "меню")))
37 | async def menu_cmd(message: types.Message, session: AsyncSession):
38 | for product in await orm_get_products(session):
39 | await message.answer_photo(
40 | product.image,
41 | caption=f"{product.name}\
42 | \n{product.description}\nСтоимость: {round(product.price, 2)}",
43 | )
44 | await message.answer("Вот меню:")
45 |
46 |
47 | @user_private_router.message(F.text.lower() == "о магазине")
48 | @user_private_router.message(Command("about"))
49 | async def about_cmd(message: types.Message):
50 | await message.answer("О нас:")
51 |
52 |
53 | @user_private_router.message(F.text.lower() == "варианты оплаты")
54 | @user_private_router.message(Command("payment"))
55 | async def payment_cmd(message: types.Message):
56 | text = as_marked_section(
57 | Bold("Варианты оплаты:"),
58 | "Картой в боте",
59 | "При получении карта/кеш",
60 | "В заведении",
61 | marker="✅ ",
62 | )
63 | await message.answer(text.as_html())
64 |
65 |
66 | @user_private_router.message(
67 | (F.text.lower().contains("доставк")) | (F.text.lower() == "варианты доставки"))
68 | @user_private_router.message(Command("shipping"))
69 | async def shipping_cmd(message: types.Message):
70 | text = as_list(
71 | as_marked_section(
72 | Bold("Варианты доставки/заказа:"),
73 | "Курьер",
74 | "Самовынос (сейчас прибегу заберу)",
75 | "Покушаю у Вас (сейчас прибегу)",
76 | marker="✅ ",
77 | ),
78 | as_marked_section(
79 | Bold("Нельзя:"),
80 | "Почта",
81 | "Голуби",
82 | marker="❌ "
83 | ),
84 | sep="\n----------------------\n",
85 | )
86 | await message.answer(text.as_html())
87 |
88 |
89 | # @user_private_router.message(F.contact)
90 | # async def get_contact(message: types.Message):
91 | # await message.answer(f"номер получен")
92 | # await message.answer(str(message.contact))
93 |
94 |
95 | # @user_private_router.message(F.location)
96 | # async def get_location(message: types.Message):
97 | # await message.answer(f"локация получена")
98 | # await message.answer(str(message.location))
99 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/handlers/menu_processing.py:
--------------------------------------------------------------------------------
1 | from aiogram.types import InputMediaPhoto
2 | from sqlalchemy.ext.asyncio import AsyncSession
3 |
4 | from database.orm_query import (
5 | orm_add_to_cart,
6 | orm_delete_from_cart,
7 | orm_get_banner,
8 | orm_get_categories,
9 | orm_get_products,
10 | orm_get_user_carts,
11 | orm_reduce_product_in_cart,
12 | )
13 | from kbds.inline import (
14 | get_products_btns,
15 | get_user_cart,
16 | get_user_catalog_btns,
17 | get_user_main_btns,
18 | )
19 |
20 | from utils.paginator import Paginator
21 |
22 |
23 | async def main_menu(session, level, menu_name):
24 | banner = await orm_get_banner(session, menu_name)
25 | image = InputMediaPhoto(media=banner.image, caption=banner.description)
26 |
27 | kbds = get_user_main_btns(level=level)
28 |
29 | return image, kbds
30 |
31 |
32 | async def catalog(session, level, menu_name):
33 | banner = await orm_get_banner(session, menu_name)
34 | image = InputMediaPhoto(media=banner.image, caption=banner.description)
35 |
36 | categories = await orm_get_categories(session)
37 | kbds = get_user_catalog_btns(level=level, categories=categories)
38 |
39 | return image, kbds
40 |
41 |
42 | def pages(paginator: Paginator):
43 | btns = dict()
44 | if paginator.has_previous():
45 | btns["◀ Пред."] = "previous"
46 |
47 | if paginator.has_next():
48 | btns["След. ▶"] = "next"
49 |
50 | return btns
51 |
52 |
53 | async def products(session, level, category, page):
54 | products = await orm_get_products(session, category_id=category)
55 |
56 | paginator = Paginator(products, page=page)
57 | product = paginator.get_page()[0]
58 |
59 | image = InputMediaPhoto(
60 | media=product.image,
61 | caption=f"{product.name}\
62 | \n{product.description}\nСтоимость: {round(product.price, 2)}\n\
63 | Товар {paginator.page} из {paginator.pages}",
64 | )
65 |
66 | pagination_btns = pages(paginator)
67 |
68 | kbds = get_products_btns(
69 | level=level,
70 | category=category,
71 | page=page,
72 | pagination_btns=pagination_btns,
73 | product_id=product.id,
74 | )
75 |
76 | return image, kbds
77 |
78 |
79 | async def carts(session, level, menu_name, page, user_id, product_id):
80 | if menu_name == "delete":
81 | await orm_delete_from_cart(session, user_id, product_id)
82 | if page > 1:
83 | page -= 1
84 | elif menu_name == "decrement":
85 | is_cart = await orm_reduce_product_in_cart(session, user_id, product_id)
86 | if page > 1 and not is_cart:
87 | page -= 1
88 | elif menu_name == "increment":
89 | await orm_add_to_cart(session, user_id, product_id)
90 |
91 | carts = await orm_get_user_carts(session, user_id)
92 |
93 | if not carts:
94 | banner = await orm_get_banner(session, "cart")
95 | image = InputMediaPhoto(
96 | media=banner.image, caption=f"{banner.description}"
97 | )
98 |
99 | kbds = get_user_cart(
100 | level=level,
101 | page=None,
102 | pagination_btns=None,
103 | product_id=None,
104 | )
105 |
106 | else:
107 | paginator = Paginator(carts, page=page)
108 |
109 | cart = paginator.get_page()[0]
110 |
111 | cart_price = round(cart.quantity * cart.product.price, 2)
112 | total_price = round(
113 | sum(cart.quantity * cart.product.price for cart in carts), 2
114 | )
115 | image = InputMediaPhoto(
116 | media=cart.product.image,
117 | caption=f"{cart.product.name}\n{cart.product.price}$ x {cart.quantity} = {cart_price}$\
118 | \nТовар {paginator.page} из {paginator.pages} в корзине.\nОбщая стоимость товаров в корзине {total_price}",
119 | )
120 |
121 | pagination_btns = pages(paginator)
122 |
123 | kbds = get_user_cart(
124 | level=level,
125 | page=page,
126 | pagination_btns=pagination_btns,
127 | product_id=cart.product.id,
128 | )
129 |
130 | return image, kbds
131 |
132 |
133 | async def get_menu_content(
134 | session: AsyncSession,
135 | level: int,
136 | menu_name: str,
137 | category: int | None = None,
138 | page: int | None = None,
139 | product_id: int | None = None,
140 | user_id: int | None = None,
141 | ):
142 | if level == 0:
143 | return await main_menu(session, level, menu_name)
144 | elif level == 1:
145 | return await catalog(session, level, menu_name)
146 | elif level == 2:
147 | return await products(session, level, category, page)
148 | elif level == 3:
149 | return await carts(session, level, menu_name, page, user_id, product_id)
150 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/kbds/inline.py:
--------------------------------------------------------------------------------
1 | from aiogram.filters.callback_data import CallbackData
2 | from aiogram.types import InlineKeyboardButton
3 | from aiogram.utils.keyboard import InlineKeyboardBuilder
4 |
5 |
6 | class MenuCallBack(CallbackData, prefix="menu"):
7 | level: int
8 | menu_name: str
9 | category: int | None = None
10 | page: int = 1
11 | product_id: int | None = None
12 |
13 |
14 | def get_user_main_btns(*, level: int, sizes: tuple[int] = (2,)):
15 | keyboard = InlineKeyboardBuilder()
16 | btns = {
17 | "Товары 🍕": "catalog",
18 | "Корзина 🛒": "cart",
19 | "О нас ℹ️": "about",
20 | "Оплата 💰": "payment",
21 | "Доставка ⛵": "shipping",
22 | }
23 | for text, menu_name in btns.items():
24 | if menu_name == 'catalog':
25 | keyboard.add(InlineKeyboardButton(text=text,
26 | callback_data=MenuCallBack(level=level+1, menu_name=menu_name).pack()))
27 | elif menu_name == 'cart':
28 | keyboard.add(InlineKeyboardButton(text=text,
29 | callback_data=MenuCallBack(level=3, menu_name=menu_name).pack()))
30 | else:
31 | keyboard.add(InlineKeyboardButton(text=text,
32 | callback_data=MenuCallBack(level=level, menu_name=menu_name).pack()))
33 |
34 | return keyboard.adjust(*sizes).as_markup()
35 |
36 |
37 | def get_user_catalog_btns(*, level: int, categories: list, sizes: tuple[int] = (2,)):
38 | keyboard = InlineKeyboardBuilder()
39 |
40 | keyboard.add(InlineKeyboardButton(text='Назад',
41 | callback_data=MenuCallBack(level=level-1, menu_name='main').pack()))
42 | keyboard.add(InlineKeyboardButton(text='Корзина 🛒',
43 | callback_data=MenuCallBack(level=3, menu_name='cart').pack()))
44 |
45 | for c in categories:
46 | keyboard.add(InlineKeyboardButton(text=c.name,
47 | callback_data=MenuCallBack(level=level+1, menu_name=c.name, category=c.id).pack()))
48 |
49 | return keyboard.adjust(*sizes).as_markup()
50 |
51 |
52 | def get_products_btns(
53 | *,
54 | level: int,
55 | category: int,
56 | page: int,
57 | pagination_btns: dict,
58 | product_id: int,
59 | sizes: tuple[int] = (2, 1)
60 | ):
61 | keyboard = InlineKeyboardBuilder()
62 |
63 | keyboard.add(InlineKeyboardButton(text='Назад',
64 | callback_data=MenuCallBack(level=level-1, menu_name='catalog').pack()))
65 | keyboard.add(InlineKeyboardButton(text='Корзина 🛒',
66 | callback_data=MenuCallBack(level=3, menu_name='cart').pack()))
67 | keyboard.add(InlineKeyboardButton(text='Купить 💵',
68 | callback_data=MenuCallBack(level=level, menu_name='add_to_cart', product_id=product_id).pack()))
69 |
70 | keyboard.adjust(*sizes)
71 |
72 | row = []
73 | for text, menu_name in pagination_btns.items():
74 | if menu_name == "next":
75 | row.append(InlineKeyboardButton(text=text,
76 | callback_data=MenuCallBack(
77 | level=level,
78 | menu_name=menu_name,
79 | category=category,
80 | page=page + 1).pack()))
81 |
82 | elif menu_name == "previous":
83 | row.append(InlineKeyboardButton(text=text,
84 | callback_data=MenuCallBack(
85 | level=level,
86 | menu_name=menu_name,
87 | category=category,
88 | page=page - 1).pack()))
89 |
90 | return keyboard.row(*row).as_markup()
91 |
92 |
93 | def get_user_cart(
94 | *,
95 | level: int,
96 | page: int | None,
97 | pagination_btns: dict | None,
98 | product_id: int | None,
99 | sizes: tuple[int] = (3,)
100 | ):
101 | keyboard = InlineKeyboardBuilder()
102 | if page:
103 | keyboard.add(InlineKeyboardButton(text='Удалить',
104 | callback_data=MenuCallBack(level=level, menu_name='delete', product_id=product_id, page=page).pack()))
105 | keyboard.add(InlineKeyboardButton(text='-1',
106 | callback_data=MenuCallBack(level=level, menu_name='decrement', product_id=product_id, page=page).pack()))
107 | keyboard.add(InlineKeyboardButton(text='+1',
108 | callback_data=MenuCallBack(level=level, menu_name='increment', product_id=product_id, page=page).pack()))
109 |
110 | keyboard.adjust(*sizes)
111 |
112 | row = []
113 | for text, menu_name in pagination_btns.items():
114 | if menu_name == "next":
115 | row.append(InlineKeyboardButton(text=text,
116 | callback_data=MenuCallBack(level=level, menu_name=menu_name, page=page + 1).pack()))
117 | elif menu_name == "previous":
118 | row.append(InlineKeyboardButton(text=text,
119 | callback_data=MenuCallBack(level=level, menu_name=menu_name, page=page - 1).pack()))
120 |
121 | keyboard.row(*row)
122 |
123 | row2 = [
124 | InlineKeyboardButton(text='На главную 🏠',
125 | callback_data=MenuCallBack(level=0, menu_name='main').pack()),
126 | InlineKeyboardButton(text='Заказать',
127 | callback_data=MenuCallBack(level=0, menu_name='order').pack()),
128 | ]
129 | return keyboard.row(*row2).as_markup()
130 | else:
131 | keyboard.add(
132 | InlineKeyboardButton(text='На главную 🏠',
133 | callback_data=MenuCallBack(level=0, menu_name='main').pack()))
134 |
135 | return keyboard.adjust(*sizes).as_markup()
136 |
137 |
138 | def get_callback_btns(*, btns: dict[str, str], sizes: tuple[int] = (2,)):
139 | keyboard = InlineKeyboardBuilder()
140 |
141 | for text, data in btns.items():
142 | keyboard.add(InlineKeyboardButton(text=text, callback_data=data))
143 |
144 | return keyboard.adjust(*sizes).as_markup()
145 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/database/orm_query.py:
--------------------------------------------------------------------------------
1 | import math
2 | from sqlalchemy import select, update, delete
3 | from sqlalchemy.ext.asyncio import AsyncSession
4 | from sqlalchemy.orm import joinedload
5 |
6 | from database.models import Banner, Cart, Category, Product, User
7 |
8 |
9 | ############### Работа с баннерами (информационными страницами) ###############
10 |
11 | async def orm_add_banner_description(session: AsyncSession, data: dict):
12 | #Добавляем новый или изменяем существующий по именам
13 | #пунктов меню: main, about, cart, shipping, payment, catalog
14 | query = select(Banner)
15 | result = await session.execute(query)
16 | if result.first():
17 | return
18 | session.add_all([Banner(name=name, description=description) for name, description in data.items()])
19 | await session.commit()
20 |
21 |
22 | async def orm_change_banner_image(session: AsyncSession, name: str, image: str):
23 | query = update(Banner).where(Banner.name == name).values(image=image)
24 | await session.execute(query)
25 | await session.commit()
26 |
27 |
28 | async def orm_get_banner(session: AsyncSession, page: str):
29 | query = select(Banner).where(Banner.name == page)
30 | result = await session.execute(query)
31 | return result.scalar()
32 |
33 |
34 | async def orm_get_info_pages(session: AsyncSession):
35 | query = select(Banner)
36 | result = await session.execute(query)
37 | return result.scalars().all()
38 |
39 |
40 | ############################ Категории ######################################
41 |
42 | async def orm_get_categories(session: AsyncSession):
43 | query = select(Category)
44 | result = await session.execute(query)
45 | return result.scalars().all()
46 |
47 | async def orm_create_categories(session: AsyncSession, categories: list):
48 | query = select(Category)
49 | result = await session.execute(query)
50 | if result.first():
51 | return
52 | session.add_all([Category(name=name) for name in categories])
53 | await session.commit()
54 |
55 | ############ Админка: добавить/изменить/удалить товар ########################
56 |
57 | async def orm_add_product(session: AsyncSession, data: dict):
58 | obj = Product(
59 | name=data["name"],
60 | description=data["description"],
61 | price=float(data["price"]),
62 | image=data["image"],
63 | category_id=int(data["category"]),
64 | )
65 | session.add(obj)
66 | await session.commit()
67 |
68 |
69 | async def orm_get_products(session: AsyncSession, category_id):
70 | query = select(Product).where(Product.category_id == int(category_id))
71 | result = await session.execute(query)
72 | return result.scalars().all()
73 |
74 |
75 | async def orm_get_product(session: AsyncSession, product_id: int):
76 | query = select(Product).where(Product.id == product_id)
77 | result = await session.execute(query)
78 | return result.scalar()
79 |
80 |
81 | async def orm_update_product(session: AsyncSession, product_id: int, data):
82 | query = (
83 | update(Product)
84 | .where(Product.id == product_id)
85 | .values(
86 | name=data["name"],
87 | description=data["description"],
88 | price=float(data["price"]),
89 | image=data["image"],
90 | category_id=int(data["category"]),
91 | )
92 | )
93 | await session.execute(query)
94 | await session.commit()
95 |
96 |
97 | async def orm_delete_product(session: AsyncSession, product_id: int):
98 | query = delete(Product).where(Product.id == product_id)
99 | await session.execute(query)
100 | await session.commit()
101 |
102 | ##################### Добавляем юзера в БД #####################################
103 |
104 | async def orm_add_user(
105 | session: AsyncSession,
106 | user_id: int,
107 | first_name: str | None = None,
108 | last_name: str | None = None,
109 | phone: str | None = None,
110 | ):
111 | query = select(User).where(User.user_id == user_id)
112 | result = await session.execute(query)
113 | if result.first() is None:
114 | session.add(
115 | User(user_id=user_id, first_name=first_name, last_name=last_name, phone=phone)
116 | )
117 | await session.commit()
118 |
119 |
120 | ######################## Работа с корзинами #######################################
121 |
122 | async def orm_add_to_cart(session: AsyncSession, user_id: int, product_id: int):
123 | query = select(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id)
124 | cart = await session.execute(query)
125 | cart = cart.scalar()
126 | if cart:
127 | cart.quantity += 1
128 | await session.commit()
129 | return cart
130 | else:
131 | session.add(Cart(user_id=user_id, product_id=product_id, quantity=1))
132 | await session.commit()
133 |
134 |
135 |
136 | async def orm_get_user_carts(session: AsyncSession, user_id):
137 | query = select(Cart).filter(Cart.user_id == user_id).options(joinedload(Cart.product))
138 | result = await session.execute(query)
139 | return result.scalars().all()
140 |
141 |
142 | async def orm_delete_from_cart(session: AsyncSession, user_id: int, product_id: int):
143 | query = delete(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id)
144 | await session.execute(query)
145 | await session.commit()
146 |
147 |
148 | async def orm_reduce_product_in_cart(session: AsyncSession, user_id: int, product_id: int):
149 | query = select(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id)
150 | cart = await session.execute(query)
151 | cart = cart.scalar()
152 |
153 | if not cart:
154 | return
155 | if cart.quantity > 1:
156 | cart.quantity -= 1
157 | await session.commit()
158 | return True
159 | else:
160 | await orm_delete_from_cart(session, user_id, product_id)
161 | await session.commit()
162 | return False
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/lesson_6-FSM/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, Router, types
2 | from aiogram.filters import Command, StateFilter
3 | from aiogram.fsm.context import FSMContext
4 | from aiogram.fsm.state import State, StatesGroup
5 |
6 | from filters.chat_types import ChatTypeFilter, IsAdmin
7 | from kbds.reply import get_keyboard
8 |
9 |
10 | admin_router = Router()
11 | admin_router.message.filter(ChatTypeFilter(["private"]), IsAdmin())
12 |
13 |
14 | ADMIN_KB = get_keyboard(
15 | "Добавить товар",
16 | "Изменить товар",
17 | "Удалить товар",
18 | "Я так, просто посмотреть зашел",
19 | placeholder="Выберите действие",
20 | sizes=(2, 1, 1),
21 | )
22 |
23 |
24 | @admin_router.message(Command("admin"))
25 | async def admin_features(message: types.Message):
26 | await message.answer("Что хотите сделать?", reply_markup=ADMIN_KB)
27 |
28 |
29 | @admin_router.message(F.text == "Я так, просто посмотреть зашел")
30 | async def starring_at_product(message: types.Message):
31 | await message.answer("ОК, вот список товаров")
32 |
33 |
34 | @admin_router.message(F.text == "Изменить товар")
35 | async def change_product(message: types.Message):
36 | await message.answer("ОК, вот список товаров")
37 |
38 |
39 | @admin_router.message(F.text == "Удалить товар")
40 | async def delete_product(message: types.Message):
41 | await message.answer("Выберите товар(ы) для удаления")
42 |
43 |
44 | #Код ниже для машины состояний (FSM)
45 |
46 | class AddProduct(StatesGroup):
47 | #Шаги состояний
48 | name = State()
49 | description = State()
50 | price = State()
51 | image = State()
52 |
53 | texts = {
54 | 'AddProduct:name': 'Введите название заново:',
55 | 'AddProduct:description': 'Введите описание заново:',
56 | 'AddProduct:price': 'Введите стоимость заново:',
57 | 'AddProduct:image': 'Этот стейт последний, поэтому...',
58 | }
59 |
60 | #Становимся в состояние ожидания ввода name
61 | @admin_router.message(StateFilter(None), F.text == "Добавить товар")
62 | async def add_product(message: types.Message, state: FSMContext):
63 | await message.answer(
64 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
65 | )
66 | await state.set_state(AddProduct.name)
67 |
68 |
69 | #Хендлер отмены и сброса состояния должен быть всегда именно хдесь,
70 | #после того как только встали в состояние номер 1 (элементарная очередность фильтров)
71 | @admin_router.message(StateFilter('*'), Command("отмена"))
72 | @admin_router.message(StateFilter('*'), F.text.casefold() == "отмена")
73 | async def cancel_handler(message: types.Message, state: FSMContext) -> None:
74 |
75 | current_state = await state.get_state()
76 | if current_state is None:
77 | return
78 |
79 | await state.clear()
80 | await message.answer("Действия отменены", reply_markup=ADMIN_KB)
81 |
82 | #Вернутся на шаг назад (на прошлое состояние)
83 | @admin_router.message(StateFilter('*'), Command("назад"))
84 | @admin_router.message(StateFilter('*'), F.text.casefold() == "назад")
85 | async def back_step_handler(message: types.Message, state: FSMContext) -> None:
86 |
87 | current_state = await state.get_state()
88 |
89 | if current_state == AddProduct.name:
90 | await message.answer('Предидущего шага нет, или введите название товара или напишите "отмена"')
91 | return
92 |
93 | previous = None
94 | for step in AddProduct.__all_states__:
95 | if step.state == current_state:
96 | await state.set_state(previous)
97 | await message.answer(f"Ок, вы вернулись к прошлому шагу \n {AddProduct.texts[previous.state]}")
98 | return
99 | previous = step
100 |
101 |
102 | #Ловим данные для состояние name и потом меняем состояние на description
103 | @admin_router.message(AddProduct.name, F.text)
104 | async def add_name(message: types.Message, state: FSMContext):
105 | # Здесь можно сделать какую либо дополнительную проверку
106 | #и выйти из хендлера не меняя состояние с отправкой соответствующего сообщения
107 | #например:
108 | if len(message.text) >= 100:
109 | await message.answer("Название товара не должно превышать 100 символов. \n Введите заново")
110 | return
111 |
112 | await state.update_data(name=message.text)
113 | await message.answer("Введите описание товара")
114 | await state.set_state(AddProduct.description)
115 |
116 | #Хендлер для отлова некорректных вводов для состояния name
117 | @admin_router.message(AddProduct.name)
118 | async def add_name2(message: types.Message, state: FSMContext):
119 | await message.answer("Вы ввели не допустимые данные, введите текст названия товара")
120 |
121 |
122 |
123 | #Ловим данные для состояние description и потом меняем состояние на price
124 | @admin_router.message(AddProduct.description, F.text)
125 | async def add_description(message: types.Message, state: FSMContext):
126 | await state.update_data(description=message.text)
127 | await message.answer("Введите стоимость товара")
128 | await state.set_state(AddProduct.price)
129 |
130 | #Хендлер для отлова некорректных вводов для состояния description
131 | @admin_router.message(AddProduct.description)
132 | async def add_description2(message: types.Message, state: FSMContext):
133 | await message.answer("Вы ввели не допустимые данные, введите текст описания товара")
134 |
135 |
136 |
137 | #Ловим данные для состояние price и потом меняем состояние на image
138 | @admin_router.message(AddProduct.price, F.text)
139 | async def add_price(message: types.Message, state: FSMContext):
140 | try:
141 | float(message.text)
142 | except ValueError:
143 | await message.answer("Введите корректное значение цены")
144 | return
145 |
146 | await state.update_data(price=message.text)
147 | await message.answer("Загрузите изображение товара")
148 | await state.set_state(AddProduct.image)
149 |
150 | #Хендлер для отлова некорректных ввода для состояния price
151 | @admin_router.message(AddProduct.price)
152 | async def add_price2(message: types.Message, state: FSMContext):
153 | await message.answer("Вы ввели не допустимые данные, введите стоимость товара")
154 |
155 |
156 |
157 | #Ловим данные для состояние image и потом выходим из состояний
158 | @admin_router.message(AddProduct.image, F.photo)
159 | async def add_image(message: types.Message, state: FSMContext):
160 | await state.update_data(image=message.photo[-1].file_id)
161 | await message.answer("Товар добавлен", reply_markup=ADMIN_KB)
162 | data = await state.get_data()
163 | await message.answer(str(data))
164 | await state.clear()
165 |
166 | @admin_router.message(AddProduct.image)
167 | async def add_image2(message: types.Message, state: FSMContext):
168 | await message.answer("Отправьте фото пищи")
169 |
--------------------------------------------------------------------------------
/lesson_8 template/database/orm_query.py:
--------------------------------------------------------------------------------
1 | import math
2 | from sqlalchemy import select, update, delete
3 | from sqlalchemy.ext.asyncio import AsyncSession
4 | from sqlalchemy.orm import joinedload
5 |
6 | from database.models import Banner, Cart, Category, Product, User
7 |
8 | # Простой пагинатор
9 | class Paginator:
10 | def __init__(self, array: list | tuple, page: int=1, per_page: int=1):
11 | self.array = array
12 | self.per_page = per_page
13 | self.page = page
14 | self.len = len(self.array)
15 | # math.ceil - округление в большую сторону до целого числа
16 | self.pages = math.ceil(self.len / self.per_page)
17 |
18 | def __get_slice(self):
19 | start = (self.page - 1) * self.per_page
20 | stop = start + self.per_page
21 | return self.array[start:stop]
22 |
23 | def get_page(self):
24 | page_items = self.__get_slice()
25 | return page_items
26 |
27 | def has_next(self):
28 | if self.page < self.pages:
29 | return self.page + 1
30 | return False
31 |
32 | def has_previous(self):
33 | if self.page > 1:
34 | return self.page - 1
35 | return False
36 |
37 | def get_next(self):
38 | if self.page < self.pages:
39 | self.page += 1
40 | return self.get_page()
41 | raise IndexError(f'Next page does not exist. Use has_next() to check before.')
42 |
43 | def get_previous(self):
44 | if self.page > 1:
45 | self.page -= 1
46 | return self.__get_slice()
47 | raise IndexError(f'Previous page does not exist. Use has_previous() to check before.')
48 |
49 |
50 | ############### Работа с баннерами (информационными страницами) ###############
51 |
52 | async def orm_add_banner_description(session: AsyncSession, data: dict):
53 | #Добавляем новый или изменяем существующий по именам
54 | #пунктов меню: main, about, cart, shipping, payment, catalog
55 | query = select(Banner)
56 | result = await session.execute(query)
57 | if result.first():
58 | return
59 | session.add_all([Banner(name=name, description=description) for name, description in data.items()])
60 | await session.commit()
61 |
62 |
63 | async def orm_change_banner_image(session: AsyncSession, name: str, image: str):
64 | query = update(Banner).where(Banner.name == name).values(image=image)
65 | await session.execute(query)
66 | await session.commit()
67 |
68 |
69 | async def orm_get_banner(session: AsyncSession, page: str):
70 | query = select(Banner).where(Banner.name == page)
71 | result = await session.execute(query)
72 | return result.scalar()
73 |
74 |
75 | async def orm_get_info_pages(session: AsyncSession):
76 | query = select(Banner)
77 | result = await session.execute(query)
78 | return result.scalars().all()
79 |
80 |
81 | ############################ Категории ######################################
82 |
83 | async def orm_get_categories(session: AsyncSession):
84 | query = select(Category)
85 | result = await session.execute(query)
86 | return result.scalars().all()
87 |
88 | async def orm_create_categories(session: AsyncSession, categories: list):
89 | query = select(Category)
90 | result = await session.execute(query)
91 | if result.first():
92 | return
93 | session.add_all([Category(name=name) for name in categories])
94 | await session.commit()
95 |
96 | ############ Админка: добавить/изменить/удалить товар ########################
97 |
98 | async def orm_add_product(session: AsyncSession, data: dict):
99 | obj = Product(
100 | name=data["name"],
101 | description=data["description"],
102 | price=float(data["price"]),
103 | image=data["image"],
104 | category_id=int(data["category"]),
105 | )
106 | session.add(obj)
107 | await session.commit()
108 |
109 |
110 | async def orm_get_products(session: AsyncSession, category_id):
111 | query = select(Product).where(Product.category_id == int(category_id))
112 | result = await session.execute(query)
113 | return result.scalars().all()
114 |
115 |
116 | async def orm_get_product(session: AsyncSession, product_id: int):
117 | query = select(Product).where(Product.id == product_id)
118 | result = await session.execute(query)
119 | return result.scalar()
120 |
121 |
122 | async def orm_update_product(session: AsyncSession, product_id: int, data):
123 | query = (
124 | update(Product)
125 | .where(Product.id == product_id)
126 | .values(
127 | name=data["name"],
128 | description=data["description"],
129 | price=float(data["price"]),
130 | image=data["image"],
131 | category_id=int(data["category"]),
132 | )
133 | )
134 | await session.execute(query)
135 | await session.commit()
136 |
137 |
138 | async def orm_delete_product(session: AsyncSession, product_id: int):
139 | query = delete(Product).where(Product.id == product_id)
140 | await session.execute(query)
141 | await session.commit()
142 |
143 | ##################### Добавляем юзера в БД #####################################
144 |
145 | async def orm_add_user(
146 | session: AsyncSession,
147 | user_id: int,
148 | first_name: str | None = None,
149 | last_name: str | None = None,
150 | phone: str | None = None,
151 | ):
152 | query = select(User).where(User.user_id == user_id)
153 | result = await session.execute(query)
154 | if result.first() is None:
155 | session.add(
156 | User(user_id=user_id, first_name=first_name, last_name=last_name, phone=phone)
157 | )
158 | await session.commit()
159 |
160 |
161 | ######################## Работа с корзинами #######################################
162 |
163 | async def orm_add_to_cart(session: AsyncSession, user_id: int, product_id: int):
164 | query = select(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id).options(joinedload(Cart.product))
165 | cart = await session.execute(query)
166 | cart = cart.scalar()
167 | if cart:
168 | cart.quantity += 1
169 | await session.commit()
170 | return cart
171 | else:
172 | session.add(Cart(user_id=user_id, product_id=product_id, quantity=1))
173 | await session.commit()
174 |
175 |
176 |
177 | async def orm_get_user_carts(session: AsyncSession, user_id):
178 | query = select(Cart).filter(Cart.user_id == user_id).options(joinedload(Cart.product))
179 | result = await session.execute(query)
180 | return result.scalars().all()
181 |
182 |
183 | async def orm_delete_from_cart(session: AsyncSession, user_id: int, product_id: int):
184 | query = delete(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id)
185 | await session.execute(query)
186 | await session.commit()
187 |
188 |
189 | async def orm_reduce_product_in_cart(session: AsyncSession, user_id: int, product_id: int):
190 | query = select(Cart).where(Cart.user_id == user_id, Cart.product_id == product_id).options(joinedload(Cart.product))
191 | cart = await session.execute(query)
192 | cart = cart.scalar()
193 |
194 | if not cart:
195 | return
196 | if cart.quantity > 1:
197 | cart.quantity -= 1
198 | await session.commit()
199 | return True
200 | else:
201 | await orm_delete_from_cart(session, user_id, product_id)
202 | await session.commit()
203 | return False
204 |
205 |
206 |
207 |
--------------------------------------------------------------------------------
/lesson_7/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, Router, types
2 | from aiogram.filters import Command, StateFilter, or_f
3 | from aiogram.fsm.context import FSMContext
4 | from aiogram.fsm.state import State, StatesGroup
5 |
6 | from sqlalchemy.ext.asyncio import AsyncSession
7 |
8 | from database.orm_query import (
9 | orm_add_product,
10 | orm_delete_product,
11 | orm_get_product,
12 | orm_get_products,
13 | orm_update_product,
14 | )
15 |
16 | from filters.chat_types import ChatTypeFilter, IsAdmin
17 |
18 | from kbds.inline import get_callback_btns
19 | from kbds.reply import get_keyboard
20 |
21 |
22 | admin_router = Router()
23 | admin_router.message.filter(ChatTypeFilter(["private"]), IsAdmin())
24 |
25 |
26 | ADMIN_KB = get_keyboard(
27 | "Добавить товар",
28 | "Ассортимент",
29 | placeholder="Выберите действие",
30 | sizes=(2,),
31 | )
32 |
33 |
34 | class AddProduct(StatesGroup):
35 | # Шаги состояний
36 | name = State()
37 | description = State()
38 | price = State()
39 | image = State()
40 |
41 | product_for_change = None
42 |
43 | texts = {
44 | "AddProduct:name": "Введите название заново:",
45 | "AddProduct:description": "Введите описание заново:",
46 | "AddProduct:price": "Введите стоимость заново:",
47 | "AddProduct:image": "Этот стейт последний, поэтому...",
48 | }
49 |
50 |
51 | @admin_router.message(Command("admin"))
52 | async def admin_features(message: types.Message):
53 | await message.answer("Что хотите сделать?", reply_markup=ADMIN_KB)
54 |
55 |
56 | @admin_router.message(F.text == "Ассортимент")
57 | async def starring_at_product(message: types.Message, session: AsyncSession):
58 | for product in await orm_get_products(session):
59 | await message.answer_photo(
60 | product.image,
61 | caption=f"{product.name}\
62 | \n{product.description}\nСтоимость: {round(product.price, 2)}",
63 | reply_markup=get_callback_btns(
64 | btns={
65 | "Удалить": f"delete_{product.id}",
66 | "Изменить": f"change_{product.id}",
67 | }
68 | ),
69 | )
70 | await message.answer("ОК, вот список товаров ⏫")
71 |
72 |
73 | @admin_router.callback_query(F.data.startswith("delete_"))
74 | async def delete_product_callback(callback: types.CallbackQuery, session: AsyncSession):
75 | product_id = callback.data.split("_")[-1]
76 | await orm_delete_product(session, int(product_id))
77 |
78 | await callback.answer("Товар удален")
79 | await callback.message.answer("Товар удален!")
80 |
81 | # FSM:
82 |
83 | # Становимся в состояние ожидания ввода name
84 | @admin_router.callback_query(StateFilter(None), F.data.startswith("change_"))
85 | async def change_product_callback(
86 | callback: types.CallbackQuery, state: FSMContext, session: AsyncSession
87 | ):
88 | product_id = callback.data.split("_")[-1]
89 |
90 | product_for_change = await orm_get_product(session, int(product_id))
91 |
92 | AddProduct.product_for_change = product_for_change
93 |
94 | await callback.answer()
95 | await callback.message.answer(
96 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
97 | )
98 | await state.set_state(AddProduct.name)
99 |
100 |
101 | # Становимся в состояние ожидания ввода name
102 | @admin_router.message(StateFilter(None), F.text == "Добавить товар")
103 | async def add_product(message: types.Message, state: FSMContext):
104 | await message.answer(
105 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
106 | )
107 | await state.set_state(AddProduct.name)
108 |
109 |
110 | # Хендлер отмены и сброса состояния должен быть всегда именно хдесь,
111 | # после того как только встали в состояние номер 1 (элементарная очередность фильтров)
112 | @admin_router.message(StateFilter("*"), Command("отмена"))
113 | @admin_router.message(StateFilter("*"), F.text.casefold() == "отмена")
114 | async def cancel_handler(message: types.Message, state: FSMContext) -> None:
115 | current_state = await state.get_state()
116 | if current_state is None:
117 | return
118 | if AddProduct.product_for_change:
119 | AddProduct.product_for_change = None
120 | await state.clear()
121 | await message.answer("Действия отменены", reply_markup=ADMIN_KB)
122 |
123 |
124 | # Вернутся на шаг назад (на прошлое состояние)
125 | @admin_router.message(StateFilter("*"), Command("назад"))
126 | @admin_router.message(StateFilter("*"), F.text.casefold() == "назад")
127 | async def back_step_handler(message: types.Message, state: FSMContext) -> None:
128 | current_state = await state.get_state()
129 |
130 | if current_state == AddProduct.name:
131 | await message.answer(
132 | 'Предидущего шага нет, или введите название товара или напишите "отмена"'
133 | )
134 | return
135 |
136 | previous = None
137 | for step in AddProduct.__all_states__:
138 | if step.state == current_state:
139 | await state.set_state(previous)
140 | await message.answer(
141 | f"Ок, вы вернулись к прошлому шагу \n {AddProduct.texts[previous.state]}"
142 | )
143 | return
144 | previous = step
145 |
146 |
147 | # Ловим данные для состояние name и потом меняем состояние на description
148 | @admin_router.message(AddProduct.name, or_f(F.text, F.text == "."))
149 | async def add_name(message: types.Message, state: FSMContext):
150 | if message.text == ".":
151 | await state.update_data(name=AddProduct.product_for_change.name)
152 | else:
153 | # Здесь можно сделать какую либо дополнительную проверку
154 | # и выйти из хендлера не меняя состояние с отправкой соответствующего сообщения
155 | # например:
156 | if len(message.text) >= 100:
157 | await message.answer(
158 | "Название товара не должно превышать 100 символов. \n Введите заново"
159 | )
160 | return
161 |
162 | await state.update_data(name=message.text)
163 | await message.answer("Введите описание товара")
164 | await state.set_state(AddProduct.description)
165 |
166 |
167 | # Хендлер для отлова некорректных вводов для состояния name
168 | @admin_router.message(AddProduct.name)
169 | async def add_name2(message: types.Message, state: FSMContext):
170 | await message.answer("Вы ввели не допустимые данные, введите текст названия товара")
171 |
172 |
173 | # Ловим данные для состояние description и потом меняем состояние на price
174 | @admin_router.message(AddProduct.description, or_f(F.text, F.text == "."))
175 | async def add_description(message: types.Message, state: FSMContext):
176 | if message.text == ".":
177 | await state.update_data(description=AddProduct.product_for_change.description)
178 | else:
179 | await state.update_data(description=message.text)
180 | await message.answer("Введите стоимость товара")
181 | await state.set_state(AddProduct.price)
182 |
183 |
184 | # Хендлер для отлова некорректных вводов для состояния description
185 | @admin_router.message(AddProduct.description)
186 | async def add_description2(message: types.Message, state: FSMContext):
187 | await message.answer("Вы ввели не допустимые данные, введите текст описания товара")
188 |
189 |
190 | # Ловим данные для состояние price и потом меняем состояние на image
191 | @admin_router.message(AddProduct.price, or_f(F.text, F.text == "."))
192 | async def add_price(message: types.Message, state: FSMContext):
193 | if message.text == ".":
194 | await state.update_data(price=AddProduct.product_for_change.price)
195 | else:
196 | try:
197 | float(message.text)
198 | except ValueError:
199 | await message.answer("Введите корректное значение цены")
200 | return
201 |
202 | await state.update_data(price=message.text)
203 | await message.answer("Загрузите изображение товара")
204 | await state.set_state(AddProduct.image)
205 |
206 |
207 | # Хендлер для отлова некорректных ввода для состояния price
208 | @admin_router.message(AddProduct.price)
209 | async def add_price2(message: types.Message, state: FSMContext):
210 | await message.answer("Вы ввели не допустимые данные, введите стоимость товара")
211 |
212 |
213 | # Ловим данные для состояние image и потом выходим из состояний
214 | @admin_router.message(AddProduct.image, or_f(F.photo, F.text == "."))
215 | async def add_image(message: types.Message, state: FSMContext, session: AsyncSession):
216 | if message.text and message.text == ".":
217 | await state.update_data(image=AddProduct.product_for_change.image)
218 |
219 | else:
220 | await state.update_data(image=message.photo[-1].file_id)
221 | data = await state.get_data()
222 | try:
223 | if AddProduct.product_for_change:
224 | await orm_update_product(session, AddProduct.product_for_change.id, data)
225 | else:
226 | await orm_add_product(session, data)
227 | await message.answer("Товар добавлен/изменен", reply_markup=ADMIN_KB)
228 | await state.clear()
229 |
230 | except Exception as e:
231 | await message.answer(
232 | f"Ошибка: \n{str(e)}\nОбратись к программеру, он опять денег хочет",
233 | reply_markup=ADMIN_KB,
234 | )
235 | await state.clear()
236 |
237 | AddProduct.product_for_change = None
238 |
239 |
240 | @admin_router.message(AddProduct.image)
241 | async def add_image2(message: types.Message, state: FSMContext):
242 | await message.answer("Отправьте фото пищи")
243 |
--------------------------------------------------------------------------------
/lesson_8 template/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, Router, types
2 | from aiogram.filters import Command, StateFilter, or_f
3 | from aiogram.fsm.context import FSMContext
4 | from aiogram.fsm.state import State, StatesGroup
5 |
6 | from sqlalchemy.ext.asyncio import AsyncSession
7 |
8 | from database.orm_query import (
9 | orm_change_banner_image,
10 | orm_get_categories,
11 | orm_add_product,
12 | orm_delete_product,
13 | orm_get_info_pages,
14 | orm_get_product,
15 | orm_get_products,
16 | orm_update_product,
17 | )
18 |
19 | from filters.chat_types import ChatTypeFilter, IsAdmin
20 |
21 | from kbds.inline import get_callback_btns
22 | from kbds.reply import get_keyboard
23 |
24 |
25 | admin_router = Router()
26 | admin_router.message.filter(ChatTypeFilter(["private"]), IsAdmin())
27 |
28 |
29 | ADMIN_KB = get_keyboard(
30 | "Добавить товар",
31 | "Ассортимент",
32 | "Добавить/Изменить баннер",
33 | placeholder="Выберите действие",
34 | sizes=(2,),
35 | )
36 |
37 |
38 | @admin_router.message(Command("admin"))
39 | async def admin_features(message: types.Message):
40 | await message.answer("Что хотите сделать?", reply_markup=ADMIN_KB)
41 |
42 |
43 | @admin_router.message(F.text == 'Ассортимент')
44 | async def admin_features(message: types.Message, session: AsyncSession):
45 | categories = await orm_get_categories(session)
46 | btns = {category.name : f'category_{category.id}' for category in categories}
47 | await message.answer("Выберите категорию", reply_markup=get_callback_btns(btns=btns))
48 |
49 |
50 | @admin_router.callback_query(F.data.startswith('category_'))
51 | async def starring_at_product(callback: types.CallbackQuery, session: AsyncSession):
52 | category_id = callback.data.split('_')[-1]
53 | for product in await orm_get_products(session, int(category_id)):
54 | await callback.message.answer_photo(
55 | product.image,
56 | caption=f"{product.name}\
57 | \n{product.description}\nСтоимость: {round(product.price, 2)}",
58 | reply_markup=get_callback_btns(
59 | btns={
60 | "Удалить": f"delete_{product.id}",
61 | "Изменить": f"change_{product.id}",
62 | },
63 | sizes=(2,)
64 | ),
65 | )
66 | await callback.answer()
67 | await callback.message.answer("ОК, вот список товаров ⏫")
68 |
69 |
70 | @admin_router.callback_query(F.data.startswith("delete_"))
71 | async def delete_product_callback(callback: types.CallbackQuery, session: AsyncSession):
72 | product_id = callback.data.split("_")[-1]
73 | await orm_delete_product(session, int(product_id))
74 |
75 | await callback.answer("Товар удален")
76 | await callback.message.answer("Товар удален!")
77 |
78 |
79 | ################# Микро FSM для загрузки/изменения баннеров ############################
80 |
81 | class AddBanner(StatesGroup):
82 | image = State()
83 |
84 | # Отправляем перечень информационных страниц бота и становимся в состояние отправки photo
85 | @admin_router.message(StateFilter(None), F.text == 'Добавить/Изменить баннер')
86 | async def add_image2(message: types.Message, state: FSMContext, session: AsyncSession):
87 | pages_names = [page.name for page in await orm_get_info_pages(session)]
88 | await message.answer(f"Отправьте фото баннера.\nВ описании укажите для какой страницы:\
89 | \n{', '.join(pages_names)}")
90 | await state.set_state(AddBanner.image)
91 |
92 | # Добавляем/изменяем изображение в таблице (там уже есть записанные страницы по именам:
93 | # main, catalog, cart(для пустой корзины), about, payment, shipping
94 | @admin_router.message(AddBanner.image, F.photo)
95 | async def add_banner(message: types.Message, state: FSMContext, session: AsyncSession):
96 | image_id = message.photo[-1].file_id
97 | for_page = message.caption.strip()
98 | pages_names = [page.name for page in await orm_get_info_pages(session)]
99 | if for_page not in pages_names:
100 | await message.answer(f"Введите нормальное название страницы, например:\
101 | \n{', '.join(pages_names)}")
102 | return
103 | await orm_change_banner_image(session, for_page, image_id,)
104 | await message.answer("Баннер добавлен/изменен.")
105 | await state.clear()
106 |
107 | # ловим некоррекный ввод
108 | @admin_router.message(AddBanner.image)
109 | async def add_banner2(message: types.Message, state: FSMContext):
110 | await message.answer("Отправьте фото баннера или отмена")
111 |
112 | #########################################################################################
113 |
114 |
115 |
116 | ######################### FSM для дабавления/изменения товаров админом ###################
117 |
118 | class AddProduct(StatesGroup):
119 | # Шаги состояний
120 | name = State()
121 | description = State()
122 | category = State()
123 | price = State()
124 | image = State()
125 |
126 | product_for_change = None
127 |
128 | texts = {
129 | "AddProduct:name": "Введите название заново:",
130 | "AddProduct:description": "Введите описание заново:",
131 | "AddProduct:category": "Выберите категорию заново ⬆️",
132 | "AddProduct:price": "Введите стоимость заново:",
133 | "AddProduct:image": "Этот стейт последний, поэтому...",
134 | }
135 |
136 |
137 | # Становимся в состояние ожидания ввода name
138 | @admin_router.callback_query(StateFilter(None), F.data.startswith("change_"))
139 | async def change_product_callback(
140 | callback: types.CallbackQuery, state: FSMContext, session: AsyncSession
141 | ):
142 | product_id = callback.data.split("_")[-1]
143 |
144 | product_for_change = await orm_get_product(session, int(product_id))
145 |
146 | AddProduct.product_for_change = product_for_change
147 |
148 | await callback.answer()
149 | await callback.message.answer(
150 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
151 | )
152 | await state.set_state(AddProduct.name)
153 |
154 |
155 | # Становимся в состояние ожидания ввода name
156 | @admin_router.message(StateFilter(None), F.text == "Добавить товар")
157 | async def add_product(message: types.Message, state: FSMContext):
158 | await message.answer(
159 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
160 | )
161 | await state.set_state(AddProduct.name)
162 |
163 |
164 | # Хендлер отмены и сброса состояния должен быть всегда именно здесь,
165 | # после того, как только встали в состояние номер 1 (элементарная очередность фильтров)
166 | @admin_router.message(StateFilter("*"), Command("отмена"))
167 | @admin_router.message(StateFilter("*"), F.text.casefold() == "отмена")
168 | async def cancel_handler(message: types.Message, state: FSMContext) -> None:
169 | current_state = await state.get_state()
170 | if current_state is None:
171 | return
172 | if AddProduct.product_for_change:
173 | AddProduct.product_for_change = None
174 | await state.clear()
175 | await message.answer("Действия отменены", reply_markup=ADMIN_KB)
176 |
177 |
178 | # Вернутся на шаг назад (на прошлое состояние)
179 | @admin_router.message(StateFilter("*"), Command("назад"))
180 | @admin_router.message(StateFilter("*"), F.text.casefold() == "назад")
181 | async def back_step_handler(message: types.Message, state: FSMContext) -> None:
182 | current_state = await state.get_state()
183 |
184 | if current_state == AddProduct.name:
185 | await message.answer(
186 | 'Предидущего шага нет, или введите название товара или напишите "отмена"'
187 | )
188 | return
189 |
190 | previous = None
191 | for step in AddProduct.__all_states__:
192 | if step.state == current_state:
193 | await state.set_state(previous)
194 | await message.answer(
195 | f"Ок, вы вернулись к прошлому шагу \n {AddProduct.texts[previous.state]}"
196 | )
197 | return
198 | previous = step
199 |
200 |
201 | # Ловим данные для состояние name и потом меняем состояние на description
202 | @admin_router.message(AddProduct.name, F.text)
203 | async def add_name(message: types.Message, state: FSMContext):
204 | if message.text == "." and AddProduct.product_for_change:
205 | await state.update_data(name=AddProduct.product_for_change.name)
206 | else:
207 | # Здесь можно сделать какую либо дополнительную проверку
208 | # и выйти из хендлера не меняя состояние с отправкой соответствующего сообщения
209 | # например:
210 | if 4 >= len(message.text) >= 150:
211 | await message.answer(
212 | "Название товара не должно превышать 150 символов\nили быть менее 5ти символов. \n Введите заново"
213 | )
214 | return
215 |
216 | await state.update_data(name=message.text)
217 | await message.answer("Введите описание товара")
218 | await state.set_state(AddProduct.description)
219 |
220 | # Хендлер для отлова некорректных вводов для состояния name
221 | @admin_router.message(AddProduct.name)
222 | async def add_name2(message: types.Message, state: FSMContext):
223 | await message.answer("Вы ввели не допустимые данные, введите текст названия товара")
224 |
225 |
226 | # Ловим данные для состояние description и потом меняем состояние на price
227 | @admin_router.message(AddProduct.description, F.text)
228 | async def add_description(message: types.Message, state: FSMContext, session: AsyncSession):
229 | if message.text == "." and AddProduct.product_for_change:
230 | await state.update_data(description=AddProduct.product_for_change.description)
231 | else:
232 | if 4 >= len(message.text):
233 | await message.answer(
234 | "Слишком короткое описание. \n Введите заново"
235 | )
236 | return
237 | await state.update_data(description=message.text)
238 |
239 | categories = await orm_get_categories(session)
240 | btns = {category.name : str(category.id) for category in categories}
241 | await message.answer("Выберите категорию", reply_markup=get_callback_btns(btns=btns))
242 | await state.set_state(AddProduct.category)
243 |
244 | # Хендлер для отлова некорректных вводов для состояния description
245 | @admin_router.message(AddProduct.description)
246 | async def add_description2(message: types.Message, state: FSMContext):
247 | await message.answer("Вы ввели не допустимые данные, введите текст описания товара")
248 |
249 |
250 | # Ловим callback выбора категории
251 | @admin_router.callback_query(AddProduct.category)
252 | async def category_choice(callback: types.CallbackQuery, state: FSMContext , session: AsyncSession):
253 | if int(callback.data) in [category.id for category in await orm_get_categories(session)]:
254 | await callback.answer()
255 | await state.update_data(category=callback.data)
256 | await callback.message.answer('Теперь введите цену товара.')
257 | await state.set_state(AddProduct.price)
258 | else:
259 | await callback.message.answer('Выберите катеорию из кнопок.')
260 | await callback.answer()
261 |
262 | #Ловим любые некорректные действия, кроме нажатия на кнопку выбора категории
263 | @admin_router.message(AddProduct.category)
264 | async def category_choice2(message: types.Message, state: FSMContext):
265 | await message.answer("'Выберите катеорию из кнопок.'")
266 |
267 |
268 | # Ловим данные для состояние price и потом меняем состояние на image
269 | @admin_router.message(AddProduct.price, F.text)
270 | async def add_price(message: types.Message, state: FSMContext):
271 | if message.text == "." and AddProduct.product_for_change:
272 | await state.update_data(price=AddProduct.product_for_change.price)
273 | else:
274 | try:
275 | float(message.text)
276 | except ValueError:
277 | await message.answer("Введите корректное значение цены")
278 | return
279 |
280 | await state.update_data(price=message.text)
281 | await message.answer("Загрузите изображение товара")
282 | await state.set_state(AddProduct.image)
283 |
284 | # Хендлер для отлова некорректных ввода для состояния price
285 | @admin_router.message(AddProduct.price)
286 | async def add_price2(message: types.Message, state: FSMContext):
287 | await message.answer("Вы ввели не допустимые данные, введите стоимость товара")
288 |
289 |
290 | # Ловим данные для состояние image и потом выходим из состояний
291 | @admin_router.message(AddProduct.image, or_f(F.photo, F.text == "."))
292 | async def add_image(message: types.Message, state: FSMContext, session: AsyncSession):
293 | if message.text and message.text == "." and AddProduct.product_for_change:
294 | await state.update_data(image=AddProduct.product_for_change.image)
295 |
296 | elif message.photo:
297 | await state.update_data(image=message.photo[-1].file_id)
298 | else:
299 | await message.answer("Отправьте фото пищи")
300 | return
301 | data = await state.get_data()
302 | try:
303 | if AddProduct.product_for_change:
304 | await orm_update_product(session, AddProduct.product_for_change.id, data)
305 | else:
306 | await orm_add_product(session, data)
307 | await message.answer("Товар добавлен/изменен", reply_markup=ADMIN_KB)
308 | await state.clear()
309 |
310 | except Exception as e:
311 | await message.answer(
312 | f"Ошибка: \n{str(e)}\nОбратись к программеру, он опять денег хочет",
313 | reply_markup=ADMIN_KB,
314 | )
315 | await state.clear()
316 |
317 | AddProduct.product_for_change = None
318 |
319 | # Ловим все прочее некорректное поведение для этого состояния
320 | @admin_router.message(AddProduct.image)
321 | async def add_image2(message: types.Message, state: FSMContext):
322 | await message.answer("Отправьте фото пищи")
323 |
--------------------------------------------------------------------------------
/lesson_8_multi_level_inline_menu/handlers/admin_private.py:
--------------------------------------------------------------------------------
1 | from aiogram import F, Router, types
2 | from aiogram.filters import Command, StateFilter, or_f
3 | from aiogram.fsm.context import FSMContext
4 | from aiogram.fsm.state import State, StatesGroup
5 |
6 | from sqlalchemy.ext.asyncio import AsyncSession
7 |
8 | from database.orm_query import (
9 | orm_change_banner_image,
10 | orm_get_categories,
11 | orm_add_product,
12 | orm_delete_product,
13 | orm_get_info_pages,
14 | orm_get_product,
15 | orm_get_products,
16 | orm_update_product,
17 | )
18 |
19 | from filters.chat_types import ChatTypeFilter, IsAdmin
20 |
21 | from kbds.inline import get_callback_btns
22 | from kbds.reply import get_keyboard
23 |
24 |
25 | admin_router = Router()
26 | admin_router.message.filter(ChatTypeFilter(["private"]), IsAdmin())
27 |
28 |
29 | ADMIN_KB = get_keyboard(
30 | "Добавить товар",
31 | "Ассортимент",
32 | "Добавить/Изменить баннер",
33 | placeholder="Выберите действие",
34 | sizes=(2,),
35 | )
36 |
37 |
38 | @admin_router.message(Command("admin"))
39 | async def admin_features(message: types.Message):
40 | await message.answer("Что хотите сделать?", reply_markup=ADMIN_KB)
41 |
42 |
43 | @admin_router.message(F.text == 'Ассортимент')
44 | async def admin_features(message: types.Message, session: AsyncSession):
45 | categories = await orm_get_categories(session)
46 | btns = {category.name : f'category_{category.id}' for category in categories}
47 | await message.answer("Выберите категорию", reply_markup=get_callback_btns(btns=btns))
48 |
49 |
50 | @admin_router.callback_query(F.data.startswith('category_'))
51 | async def starring_at_product(callback: types.CallbackQuery, session: AsyncSession):
52 | category_id = callback.data.split('_')[-1]
53 | for product in await orm_get_products(session, int(category_id)):
54 | await callback.message.answer_photo(
55 | product.image,
56 | caption=f"{product.name}\
57 | \n{product.description}\nСтоимость: {round(product.price, 2)}",
58 | reply_markup=get_callback_btns(
59 | btns={
60 | "Удалить": f"delete_{product.id}",
61 | "Изменить": f"change_{product.id}",
62 | },
63 | sizes=(2,)
64 | ),
65 | )
66 | await callback.answer()
67 | await callback.message.answer("ОК, вот список товаров ⏫")
68 |
69 |
70 | @admin_router.callback_query(F.data.startswith("delete_"))
71 | async def delete_product_callback(callback: types.CallbackQuery, session: AsyncSession):
72 | product_id = callback.data.split("_")[-1]
73 | await orm_delete_product(session, int(product_id))
74 |
75 | await callback.answer("Товар удален")
76 | await callback.message.answer("Товар удален!")
77 |
78 |
79 | ################# Микро FSM для загрузки/изменения баннеров ############################
80 |
81 | class AddBanner(StatesGroup):
82 | image = State()
83 |
84 | # Отправляем перечень информационных страниц бота и становимся в состояние отправки photo
85 | @admin_router.message(StateFilter(None), F.text == 'Добавить/Изменить баннер')
86 | async def add_image2(message: types.Message, state: FSMContext, session: AsyncSession):
87 | pages_names = [page.name for page in await orm_get_info_pages(session)]
88 | await message.answer(f"Отправьте фото баннера.\nВ описании укажите для какой страницы:\
89 | \n{', '.join(pages_names)}")
90 | await state.set_state(AddBanner.image)
91 |
92 | # Добавляем/изменяем изображение в таблице (там уже есть записанные страницы по именам:
93 | # main, catalog, cart(для пустой корзины), about, payment, shipping
94 | @admin_router.message(AddBanner.image, F.photo)
95 | async def add_banner(message: types.Message, state: FSMContext, session: AsyncSession):
96 | image_id = message.photo[-1].file_id
97 | for_page = message.caption.strip()
98 | pages_names = [page.name for page in await orm_get_info_pages(session)]
99 | if for_page not in pages_names:
100 | await message.answer(f"Введите нормальное название страницы, например:\
101 | \n{', '.join(pages_names)}")
102 | return
103 | await orm_change_banner_image(session, for_page, image_id,)
104 | await message.answer("Баннер добавлен/изменен.")
105 | await state.clear()
106 |
107 | # ловим некоррекный ввод
108 | @admin_router.message(AddBanner.image)
109 | async def add_banner2(message: types.Message, state: FSMContext):
110 | await message.answer("Отправьте фото баннера или отмена")
111 |
112 | #########################################################################################
113 |
114 |
115 |
116 | ######################### FSM для дабавления/изменения товаров админом ###################
117 |
118 | class AddProduct(StatesGroup):
119 | # Шаги состояний
120 | name = State()
121 | description = State()
122 | category = State()
123 | price = State()
124 | image = State()
125 |
126 | product_for_change = None
127 |
128 | texts = {
129 | "AddProduct:name": "Введите название заново:",
130 | "AddProduct:description": "Введите описание заново:",
131 | "AddProduct:category": "Выберите категорию заново ⬆️",
132 | "AddProduct:price": "Введите стоимость заново:",
133 | "AddProduct:image": "Этот стейт последний, поэтому...",
134 | }
135 |
136 |
137 | # Становимся в состояние ожидания ввода name
138 | @admin_router.callback_query(StateFilter(None), F.data.startswith("change_"))
139 | async def change_product_callback(
140 | callback: types.CallbackQuery, state: FSMContext, session: AsyncSession
141 | ):
142 | product_id = callback.data.split("_")[-1]
143 |
144 | product_for_change = await orm_get_product(session, int(product_id))
145 |
146 | AddProduct.product_for_change = product_for_change
147 |
148 | await callback.answer()
149 | await callback.message.answer(
150 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
151 | )
152 | await state.set_state(AddProduct.name)
153 |
154 |
155 | # Становимся в состояние ожидания ввода name
156 | @admin_router.message(StateFilter(None), F.text == "Добавить товар")
157 | async def add_product(message: types.Message, state: FSMContext):
158 | await message.answer(
159 | "Введите название товара", reply_markup=types.ReplyKeyboardRemove()
160 | )
161 | await state.set_state(AddProduct.name)
162 |
163 |
164 | # Хендлер отмены и сброса состояния должен быть всегда именно здесь,
165 | # после того, как только встали в состояние номер 1 (элементарная очередность фильтров)
166 | @admin_router.message(StateFilter("*"), Command("отмена"))
167 | @admin_router.message(StateFilter("*"), F.text.casefold() == "отмена")
168 | async def cancel_handler(message: types.Message, state: FSMContext) -> None:
169 | current_state = await state.get_state()
170 | if current_state is None:
171 | return
172 | if AddProduct.product_for_change:
173 | AddProduct.product_for_change = None
174 | await state.clear()
175 | await message.answer("Действия отменены", reply_markup=ADMIN_KB)
176 |
177 |
178 | # Вернутся на шаг назад (на прошлое состояние)
179 | @admin_router.message(StateFilter("*"), Command("назад"))
180 | @admin_router.message(StateFilter("*"), F.text.casefold() == "назад")
181 | async def back_step_handler(message: types.Message, state: FSMContext) -> None:
182 | current_state = await state.get_state()
183 |
184 | if current_state == AddProduct.name:
185 | await message.answer(
186 | 'Предидущего шага нет, или введите название товара или напишите "отмена"'
187 | )
188 | return
189 |
190 | previous = None
191 | for step in AddProduct.__all_states__:
192 | if step.state == current_state:
193 | await state.set_state(previous)
194 | await message.answer(
195 | f"Ок, вы вернулись к прошлому шагу \n {AddProduct.texts[previous.state]}"
196 | )
197 | return
198 | previous = step
199 |
200 |
201 | # Ловим данные для состояние name и потом меняем состояние на description
202 | @admin_router.message(AddProduct.name, F.text)
203 | async def add_name(message: types.Message, state: FSMContext):
204 | if message.text == "." and AddProduct.product_for_change:
205 | await state.update_data(name=AddProduct.product_for_change.name)
206 | else:
207 | # Здесь можно сделать какую либо дополнительную проверку
208 | # и выйти из хендлера не меняя состояние с отправкой соответствующего сообщения
209 | # например:
210 | if 4 >= len(message.text) >= 150:
211 | await message.answer(
212 | "Название товара не должно превышать 150 символов\nили быть менее 5ти символов. \n Введите заново"
213 | )
214 | return
215 |
216 | await state.update_data(name=message.text)
217 | await message.answer("Введите описание товара")
218 | await state.set_state(AddProduct.description)
219 |
220 | # Хендлер для отлова некорректных вводов для состояния name
221 | @admin_router.message(AddProduct.name)
222 | async def add_name2(message: types.Message, state: FSMContext):
223 | await message.answer("Вы ввели не допустимые данные, введите текст названия товара")
224 |
225 |
226 | # Ловим данные для состояние description и потом меняем состояние на price
227 | @admin_router.message(AddProduct.description, F.text)
228 | async def add_description(message: types.Message, state: FSMContext, session: AsyncSession):
229 | if message.text == "." and AddProduct.product_for_change:
230 | await state.update_data(description=AddProduct.product_for_change.description)
231 | else:
232 | if 4 >= len(message.text):
233 | await message.answer(
234 | "Слишком короткое описание. \n Введите заново"
235 | )
236 | return
237 | await state.update_data(description=message.text)
238 |
239 | categories = await orm_get_categories(session)
240 | btns = {category.name : str(category.id) for category in categories}
241 | await message.answer("Выберите категорию", reply_markup=get_callback_btns(btns=btns))
242 | await state.set_state(AddProduct.category)
243 |
244 | # Хендлер для отлова некорректных вводов для состояния description
245 | @admin_router.message(AddProduct.description)
246 | async def add_description2(message: types.Message, state: FSMContext):
247 | await message.answer("Вы ввели не допустимые данные, введите текст описания товара")
248 |
249 |
250 | # Ловим callback выбора категории
251 | @admin_router.callback_query(AddProduct.category)
252 | async def category_choice(callback: types.CallbackQuery, state: FSMContext , session: AsyncSession):
253 | if int(callback.data) in [category.id for category in await orm_get_categories(session)]:
254 | await callback.answer()
255 | await state.update_data(category=callback.data)
256 | await callback.message.answer('Теперь введите цену товара.')
257 | await state.set_state(AddProduct.price)
258 | else:
259 | await callback.message.answer('Выберите катеорию из кнопок.')
260 | await callback.answer()
261 |
262 | #Ловим любые некорректные действия, кроме нажатия на кнопку выбора категории
263 | @admin_router.message(AddProduct.category)
264 | async def category_choice2(message: types.Message, state: FSMContext):
265 | await message.answer("'Выберите катеорию из кнопок.'")
266 |
267 |
268 | # Ловим данные для состояние price и потом меняем состояние на image
269 | @admin_router.message(AddProduct.price, F.text)
270 | async def add_price(message: types.Message, state: FSMContext):
271 | if message.text == "." and AddProduct.product_for_change:
272 | await state.update_data(price=AddProduct.product_for_change.price)
273 | else:
274 | try:
275 | float(message.text)
276 | except ValueError:
277 | await message.answer("Введите корректное значение цены")
278 | return
279 |
280 | await state.update_data(price=message.text)
281 | await message.answer("Загрузите изображение товара")
282 | await state.set_state(AddProduct.image)
283 |
284 | # Хендлер для отлова некорректных ввода для состояния price
285 | @admin_router.message(AddProduct.price)
286 | async def add_price2(message: types.Message, state: FSMContext):
287 | await message.answer("Вы ввели не допустимые данные, введите стоимость товара")
288 |
289 |
290 | # Ловим данные для состояние image и потом выходим из состояний
291 | @admin_router.message(AddProduct.image, or_f(F.photo, F.text == "."))
292 | async def add_image(message: types.Message, state: FSMContext, session: AsyncSession):
293 | if message.text and message.text == "." and AddProduct.product_for_change:
294 | await state.update_data(image=AddProduct.product_for_change.image)
295 |
296 | elif message.photo:
297 | await state.update_data(image=message.photo[-1].file_id)
298 | else:
299 | await message.answer("Отправьте фото пищи")
300 | return
301 | data = await state.get_data()
302 | try:
303 | if AddProduct.product_for_change:
304 | await orm_update_product(session, AddProduct.product_for_change.id, data)
305 | else:
306 | await orm_add_product(session, data)
307 | await message.answer("Товар добавлен/изменен", reply_markup=ADMIN_KB)
308 | await state.clear()
309 |
310 | except Exception as e:
311 | await message.answer(
312 | f"Ошибка: \n{str(e)}\nОбратись к программеру, он опять денег хочет",
313 | reply_markup=ADMIN_KB,
314 | )
315 | await state.clear()
316 |
317 | AddProduct.product_for_change = None
318 |
319 | # Ловим все прочее некорректное поведение для этого состояния
320 | @admin_router.message(AddProduct.image)
321 | async def add_image2(message: types.Message, state: FSMContext):
322 | await message.answer("Отправьте фото пищи")
323 |
--------------------------------------------------------------------------------