Обучение Python+aiogram+MySQL
September 29, 2023

Обучение #4

Хай, как и говорил начнём с удаления данных с базы данных

Давайте возьмём готовый код из предыдущего урока

from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram import Bot, Dispatcher, executor, types
from aiogram.dispatcher import FSMContext
from aiogram.types import ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton
from class_db import work_db #Импорируем класс для работы с базой данных из файла class_db.py
import test as bb #Импортируем файл test.py в переменную bb (мне так удобнее работать с ним)
import config as c #Импортируем файл config.py в переменную с (опять же мне так удобнее)



#Создаём класс бота и дистпетчера
bot = Bot(token=c.token, parse_mode=types.ParseMode.HTML) #Берём токен бота из файла config.py который импортировали ранее в переменную c
dp = Dispatcher(bot, storage=MemoryStorage())
db_work = work_db(c.host, c.port, c.user, c.password, c.database) #Создаём экземпляр класса для работы с базой данных, передав все нужные параметры из конфига


class anketa(StatesGroup):
	name = State()
	floor = State()
	age = State()


class new_values(StatesGroup):
	new_value = State()


def creat_table(): #Данную функцию будем запускать при каждом запуске кода, параметры функция не принимает
	#Для начала конектимся к базе данных:
	connection = db_work.connect_db()
	if connection: #Если класс вернул конект, а не False
		#Напишем сам запрос
		creat_table_request = 'CREATE TABLE IF NOT EXISTS `ankets`(id int AUTO_INCREMENT, name varchar(32), floor varchar(10), age int, PRIMARY KEY (id));' #И так мы написали запрос, но для чего он нам?
		#Он нам для того что-бы создать таблицу для анкет где будут следующие данные: id - это будет id анкеты (число), оно будет прописываться само (из-за AUTO_INCREMENT)
		#name типа сроки длинной не более 32 символов, floor типа строки не более 10 символов и age типа числа без ограничения
		#Первичный ключ (PRIMARY KEY) — особенное поле в таблице, которое позволяет однозначно идентифицировать каждую запись в ней
		#Запрос написан, давайте его выполним:
		status = db_work.create_table(connection, creat_table_request) #Предаём необходимые данные (Конект и сам запрос)
		#Проверяем статус создания базы данных:
		if status: #Если вернуло True значит всё хорошо (выведем в консоль что запрос выполнен)
			print('Таблица успешно создана')
			#Закрываем конект
			if db_work.connect_close(connection):
				print('Конект закрыт!')
			else: #Если нам вернуло False, значит произошла ошибка, в вспомогательном классе мы прописали вывод ошибки
				print('Ошибка указана выше')
		else: #Если вернуло False, значит не удалось создать таблицу, ошибку мы так же вывели в классе
			print('Ошибка указана выше')
	else: #Если мы не смогли подключиться к базе данных, то опять же ошибка была выведена в классе
		print('Ошибка указана выше')


def insert_table(name_, floor_, age_): #Данная функция принимает имя таблицы в которую надо записать данные (Сразу скажу т.к. мы записывать будет в ankets ропишем получение данных)
	#Для начала конектимся к базе данных:
	connection = db_work.connect_db()
	if connection: #Если класс вернул конект, а не False
		#Напишем сам запрос
		insert_table_request = 'INSERT INTO ankets (name, floor, age) VALUES (%s, %s, %s);' #И так в этом запросе мы создаём запись данных
		with connection.cursor() as cursor:
			cursor.execute(insert_table_request, (name_, floor_, age_)) #Записываем данные полученные в функцию
		connection.commit() #Сохраняем изменения
		print('Данные записаны!')
		if db_work.connect_close(connection):
			print('Конект закрыт!')
		else: #Если нам вернуло False, значит произошла ошибка, в вспомогательном классе мы прописали вывод ошибки
			print('Ошибка указана выше')
	else: #Если мы не смогли подключиться к базе данных, то опять же ошибка была выведена в классе
		print('Ошибка указана выше')


@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео
async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение
	if message.text == '/start':
		await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем команду /start и выводим клавиатуру из файлы test.py
	elif message.text == 'Информация':
		await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}')
	elif message.text == 'Заполнить анкету':
		await bot.send_message(message.chat.id, 'Введите своё имя:')
		await anketa.name.set()
	elif message.text == '/info_table':
		info_request = 'SELECT * FROM ankets WHERE id = %s'
		values = (1,)
		connection = db_work.connect_db() #Конектимся к базе данных
		if connection: #Если конект успешный
			info = db_work.info_table(connection, info_request, values)
			if info: #Если получили данные по id = 1
				await bot.send_message(message.chat.id, f'Полученные данные:\nID: {info["id"]}\nИмя: {info["name"]}\nПол: {info["floor"]}\nВозраст: {info["age"]}')
			else: #Если не смогли получить данные
				await bot.send_message(message.chat.id, 'Не удалось получить данные')
		else: #Если конект не удался
			await bot.send_message(message.chat.id, 'Не удалось подключиться к базе данных')
	elif message.text == '/new_value':
		await bot.send_message(message.chat.id, 'Введите ID для изменения возраста:')
		await new_values.new_value.set()


@dp.message_handler(state=new_values.new_value)
async def anketa_name(message: types.Message, state: FSMContext): #Как видите у нас добавился FSMContext, он нам нужен как раз для обработки машины состояния
	#Записываем ID в кеш
	await state.update_data(id_value=message.text)
	#Выводим инлайн кнопки с числами
	#Пока напишу прямо тут, в следующих уроках будем создавать функции
	await bot.send_message(message.chat.id, 'Выберите новое значение:', reply_markup=InlineKeyboardMarkup(row_width=3).add(
		InlineKeyboardButton(text='1', callback_data='new_value_1'),InlineKeyboardButton(text='2', callback_data='new_value_2'), InlineKeyboardButton(text='10', callback_data='new_value_10'),
		InlineKeyboardButton(text='25', callback_data='new_value_25')))
	#Этого достаточно



@dp.message_handler(state=anketa.name)
async def anketa_name(message: types.Message, state: FSMContext): #Как видите у нас добавился FSMContext, он нам нужен как раз для обработки машины состояния
	#Записываем имя в кеш
	await state.update_data(name=message.text)
	#Далее просим ввести пол, с выбором пола из кнопок
	await bot.send_message(message.chat.id, 'Выберите свой пол:', reply_markup=bb.menu_floor) #добавляем вывод кнопок с выбором пола
	await anketa.floor.set()


@dp.message_handler(state=anketa.floor)
async def anketa_name(message: types.Message, state: FSMContext):
	#Записываем пол в кеш
	await state.update_data(floor=message.text)
	#Далее просим ввести свой возраст
	await bot.send_message(message.chat.id, 'Введите свой возраст:', reply_markup=ReplyKeyboardRemove()) #убираем текстовую клавиатуру
	await anketa.age.set()


@dp.message_handler(state=anketa.age)
async def anketa_name(message: types.Message, state: FSMContext):
	#Получим все данные записанные в кеш
	data = await state.get_data()
	#Сортируем их по переменным
	name = data['name'] #В ковычках пишем name, потому что сами записывали в такую переменную, там может быть любая другая
	floor = data['floor'] #В ковычках пишем floor, потому что сами записывали в такую переменную, там может быть любая другая
	age = message.text #записываем ввод пользователя в переменную age
	await state.finish() #Завершаем работу с машиной состояний
	#Выведем анкету пользователю
	await bot.send_message(message.chat.id, f'Ваша анкета:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}\n\nСпасибо за уделение времени', reply_markup=bb.mark_menu) #Отправляем анкету, благодарим, и выдаём ему текстовоем меню
	#Отправим анкету администратору по его ID из config.py
	#Так же выведем ID пользователя и его юзернейм
	await bot.send_message(c.admin, f'Пользователь: {message.chat.id}\n@{message.from_user.username} заполнил анкету\nЕго данные:\n\nИмя: {name}\nПол: {floor}\nВозраст: {age}')
	insert_table(name, floor, age) #Передаём необходимые данные



@dp.callback_query_handler(lambda call: True, state='*') #Обрабатываем нажатия всех инлайн кнопок, даже когда активно ожидание ввода от пользователя
async def callback_inline(call, state: FSMContext): #В переменную call принимаем данные о нажатой кнопке
	try: #Обрабатываем ошибки, если всё успешно то мы получим переменную id_value на всю функцию
		data = await state.get_data()
		id_value = data['id_value']
		await state.finish()
	except Exception as eror: #Если будут ошибки мы их пришлём пользователю
		await bot.send_message(call.message.chat.id, f'Произошла ошибка: {eror}')
		return #Останавливаем выполнение функции
	#Приступим к обработке нажатия кнопок, т.к. у нас записано новое чило в callback_data, но начало одно и тоже
	#Нам нужно проверять есть ли в нажатой кнопке начало:
	if 'new_value_' in call.data: #В call.data содержиться callback_data
		new_value = int(call.data.replace('new_value_', '')) #С момощью этого кода мы получим число типа int, удалив из call.data не нужное нам
		#Грубыми словами с помощью replace мы заменяем new_value_ на ничего
		#Теперь давайте заменим значение в таблице по ID которое вписали изначально
		#Конектимся к базе данных:
		connection = db_work.connect_db()
		if connection: #Если конект успешен
			#Возпользуемся функцией edit_table из вспомогательного класса:
			new_value_request = 'UPDATE ankets SET age = %s WHERE id = %s'
			values = (new_value, int(id_value))
			status = db_work.edit_table(connection, new_value_request, values)
			if status: #Если успешно обновили данные
				await bot.send_message(call.message.chat.id, f'Данные для строки с ID: {id_value}\nУспешно обновлены на: {new_value}')
			else: #Если произошла ошибка
				await bot.send_message(call.message.chat.id, 'Произошла ошибка, она отобразилась в консоли!')
		else: #Если не удалось подключиться к базе данных
			await bot.send_message(call.message.chat.id, 'Не удалось подключиться к базе данных!')


if __name__ == "__main__": #Если скрипт запущен с этого файла, запускаем executor
	creat_table() #Запускаем функцию создания таблицы
	executor.start_polling(dp, skip_updates=True) #skip_updates=True - нужен для того чтобы не обрабатывать сообщения которые были присланы пользователем в тот момент когда бот был выключен

Добавим обработку команды "/delete_strok_table":

@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео
async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение
	if message.text == '/start':
		await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем команду /start и выводим клавиатуру из файлы test.py
	elif message.text == 'Информация':
		await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}')
	elif message.text == 'Заполнить анкету':
		await bot.send_message(message.chat.id, 'Введите своё имя:')
		await anketa.name.set()
	elif message.text == '/info_table':
		info_request = 'SELECT * FROM ankets WHERE id = %s'
		values = (1,)
		connection = db_work.connect_db() #Конектимся к базе данных
		if connection: #Если конект успешный
			info = db_work.info_table(connection, info_request, values)
			if info: #Если получили данные по id = 1
				await bot.send_message(message.chat.id, f'Полученные данные:\nID: {info["id"]}\nИмя: {info["name"]}\nПол: {info["floor"]}\nВозраст: {info["age"]}')
			else: #Если не смогли получить данные
				await bot.send_message(message.chat.id, 'Не удалось получить данные')
		else: #Если конект не удался
			await bot.send_message(message.chat.id, 'Не удалось подключиться к базе данных')
	elif message.text == '/new_value':
		await bot.send_message(message.chat.id, 'Введите ID для изменения возраста:')
		await new_values.new_value.set()
	elif message.text == '/delete_strok_table':
		await bot.send_message(message.chat.id, 'Введите ID для удаления:')

Создадим класс для ожидания ответа от пользователя:

class delete_strok_table(StatesGroup):
	id_strok = State()


@dp.message_handler(content_types=['text']) #Обрабатываем каждое сообщение от пользователей, добавляем content_types для обработки текста, в будущих уроках будет фото и видео
async def text(message: types.Message): #Асинхронная функция с название "text" которая принимает в переменную message, текст пользователя отправевшего сообщение
	if message.text == '/start':
		await bot.send_message(message.chat.id, 'Привет, заполни анкету, или посмотри информацйию о мне', reply_markup=bb.mark_menu) #Обрабатываем команду /start и выводим клавиатуру из файлы test.py
	elif message.text == 'Информация':
		await bot.send_message(message.chat.id, f'Сюда пишем какую либо информацию, например я хочу вывести ID пользователя\nТвой ID: {message.chat.id}')
	elif message.text == 'Заполнить анкету':
		await bot.send_message(message.chat.id, 'Введите своё имя:')
		await anketa.name.set()
	elif message.text == '/info_table':
		info_request = 'SELECT * FROM ankets WHERE id = %s'
		values = (1,)
		connection = db_work.connect_db() #Конектимся к базе данных
		if connection: #Если конект успешный
			info = db_work.info_table(connection, info_request, values)
			if info: #Если получили данные по id = 1
				await bot.send_message(message.chat.id, f'Полученные данные:\nID: {info["id"]}\nИмя: {info["name"]}\nПол: {info["floor"]}\nВозраст: {info["age"]}')
			else: #Если не смогли получить данные
				await bot.send_message(message.chat.id, 'Не удалось получить данные')
		else: #Если конект не удался
			await bot.send_message(message.chat.id, 'Не удалось подключиться к базе данных')
	elif message.text == '/new_value':
		await bot.send_message(message.chat.id, 'Введите ID для изменения возраста:')
		await new_values.new_value.set()
	elif message.text == '/delete_strok_table':
		await bot.send_message(message.chat.id, 'Введите ID для удаления:')
		await delete_strok_table.id_strok.set()

Добавим обработчик ввода пользователя:

@dp.message_handler(state=delete_strok_table.id_strok)
async def anketa_name(message: types.Message, state: FSMContext):
	await state.finish() #Завершаем работу с машиной состояний
	id_strok = message.text
	connection = db_work.connect_db()
	if connection: #Если класс вернул конект, а не False
		#Напишем сам запрос
		delete_table = 'DELETE FROM ankets WHERE id = %s'
		values = (id_strok,)
		status = db_work.delete_from_table(connection, delete_table, values)
		if status: #Если успешно обновили данные
			await bot.send_message(message.chat.id, f'Данные строки с ID: {id_strok} - Удалены!')
		else: #Если произошла ошибка
			await bot.send_message(message.chat.id, 'Произошла ошибка, она отобразилась в консоли!')
	else:
		await bot.send_message(message.chat.id, 'Не удалось подключиться к базе данных!')

Так же давайте перепишем функцию "delete_from_table":

	def delete_from_table(self, connection, delete_, values): #Функция для редактирования данных в таблице, в delete_ будем передавать текст запроса
		try:
			with connection.cursor() as cursor:
				cursor.execute(delete_, values)
			connection.commit()
			return True
		except Exception as eror:
			print(eror) #Если произошла ошибка, выводим её в консоль
			return False #Если произошла ошибка, возвращаем False

У нас в базе есть запись с ID: 1

Давайте запустим бота, и удалим её:

У нас всё работает, мы молодцы, так держать!

———————————————————————————————

По итогам опроса на канале, я понял что вам интересна работа с файлами .txt

Давайте немного изучим циклы:

Цикл while True
Данный цикл позволяет выполнять код пока мы не завершим цикл, пример:

#обозначим переменную a = 0
a = 0
#Создадим цикл, и заставим его завешиться толькко после того как переменна a будет равна 5
while True:
   print(a)
   if a == 5:
      print('Цикл завершён')
      break
   a += 1 #После каждого круга добавляем к переменной a 1
   # "a +=1 " сокращение кода: a = a + 1
#Вывод в консоль будет:
#1
#2
#3
#4
#5
#Цикл завершён

Следующий цикл for i in spisok
Данный цикл перебирает все данные из переменной spisok в переменную i, после чего завершается, пример:

spisok = [1, 2, 3, 4, 5]
for i in spisok:
    print(i)
print('Цикл завершён')
#Вывод в консоль:
#1
#2
#3
#4
#5
#Цикл завершён

И так мы изучили базовые циклы, на простых примерах, перейдём к файлам .txt:

Давайте напишем код который создаст файл index.txt, затем запишем в него данные из списка:

spisok = [1, 2, 3, 4, 5, 6]

my_file = open("index.txt", "w+") #Создали файл
for i in spisok:
   my_file.write(str(i)) #Записываем данные, меняя тип данных на строку (str)
my_file.close() #После записи, закрываем его

После чего у нас появился файл "index.txt"

Открываем его:

Данные записались в 1 строку, давайте это исправим, и запишем каждую цифру из списка в новой строке:

spisok = [1, 2, 3, 4, 5, 6]

my_file = open("index.txt", "w+") #Создали файл
for i in spisok:
   my_file.write(f'{i}\n') #Записываем данные, меняя тип данных на строку (str)
my_file.close() #После записи, закрываем его

Удаляем файл index.txt, и снова запускаем файл с кодом:

У нас всё так же появился файл index.txt, открываем его:

Успешно записали, теперь давайте прочитаем данные и выведем в консоль:

my_file = open("index.txt", "r") #Открываем файл на чтение
for line in my_file:
   print(line)
my_file.close()

Вывод в консоль:

Он так же выводит нам пустые строки (\n), давайте удалим их:

my_file = open("index.txt", "r") #Создали файл
for line in my_file:
   print(line.replace('\n', ''))
my_file.close()

Вывод в консоль:

Давайте разберём ещё 1 цикл, для удобства:

with open('index.txt', 'r') as file: #Открываем файл на чтение, в переменную file
   #После открытия файла через этот цикл нам не нужно будет его закрывать каждый раз

Напишем код который прочитает первые 3 строки файла:

# Открываем файл для чтения
with open('index.txt', 'r') as file:
    # Читаем все строки из файла
    all_lines = file.readlines()

# Выбираем первые три строки
first_three_lines = all_lines[:3]

# Выводим прочитанные строки
for line in first_three_lines:
    print(line.replace('\n', '')) #Удаляя \n

Вывод в консоль:

А теперь давайте выведем последние 3 строки:

Пишем тот же код но меняем 1 участок:

# Открываем файл для чтения
with open('index.txt', 'r') as file:
    # Читаем все строки из файла
    all_lines = file.readlines()

# Выбираем последние три строки
first_three_lines = all_lines[-3:]

# Выводим прочитанные строки
for line in first_three_lines:
    print(line.replace('\n', '')) #Удаляя \n

Вывод в консоль:

Подробнее с работой с файлами .txt вы можете ознакомиться на сайте: *КЛИКАБЕЛЬНО*

В канале в комментариях напишите что вам было бы ещё интересно...

Спасибо за внимание

С вами был @Xacker_Name_new

Поддержка: @Bsc_Black_Secret_Club_bot

Канал: @education_python_aiogram