Код на салфетке
2.22K subscribers
745 photos
14 videos
2 files
788 links
Канал для тех, кому интересно программирование на Python и не только.

Сайт: https://pressanybutton.ru/
Чат: https://t.iss.one/+Li2vbxfWo0Q4ZDk6
Заметки автора: @writeanynotes

Реклама и взаимопиар: @Murzyev1995
Сотрудничество и др.: @proDreams
Download Telegram
Django 30. Рефакторинг и допущенные ошибки

Ошибки - неотъемлемая часть программирования. Как ни старайся, всё равно допустишь ошибки, которые по началу сочтёшь мелкими и незначительными, а по факту они соберутся в "снежный ком".

Приступив к написанию одного из следующих постов, я обнаружил несколько неудобных моментов в своём коде. Этого можно было бы избежать изначально, но невнимательность сделала своё дело.

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


Директория с шаблонами.
Начнём с простого. Сейчас есть всего одно приложение blog и директория с шаблонами находится в нём, но в будущем (и как следовало бы, в прошлом) будет несколько приложений. Каждое будет отвечать за свою область и у всех будут свои шаблоны. Делать в каждом приложении свою директорию с шаблонами не очень красиво, удобнее всё собрать в одном месте.
🔥3
К слову, это даже было предусмотрено. Изначально в посте по первоначальной настройке, мы добавили в параметр TEMPLATES строку - 'DIRS': [BASE_DIR / 'templates'],.

Решение проще некуда. Достаточно просто вынести директорию templates из директории приложения в директорию уровнем выше, где находится файл manage.py.

В ней находится директория blog, отвечающая за шаблоны приложения blog. В дальнейшем будут другие директории одноимённые используемым приложениям.


Документирование.
Классов, методов и функций становится всё больше, порой даже можно забыть зачем писал что-то. По-хорошему нужна хотя бы минимальная документация для класса и методов в нём.

Задача простая и поправимая, главное не забыть писать комментарии к чему-либо сразу при написании.


Разделение на приложения.
Добрались до главной ошибки.

В Django принято разделять логические участки на отдельные приложения, например, приложение для блога, приложение для API, приложение для аутентификации и профиля пользователя и так далее.

Моя ошибка заключается в том, что я блог и API уместил в одном приложении блога. Это не критичная ошибка, всё работает, однако это создаёт проблему навигации по коду. Код разрастается, состоит из блоков для разных задач.

Для исправления этой ситуации, я создам новое приложение api_app и перенесу в него всё, что связано с API. И всё бы ничего, но у меня есть несколько моделей и они содержат данные в БД. Простого способа перенести модель из приложения в приложение с сохранением данных в новой таблице я не нашёл. Однако есть один, весьма простой, но "костыльный" способ.

Если вы ещё только в процессе разработки сайта, не "пихайте" всё в одно приложение. Продумайте "на берегу" структуру и будущие модули. Я этого не сделал, теперь буду городить "костыли". Главное вовремя найти ошибку, исправить её и выучить урок.


Создание нового приложения.
Создадим новое приложение api_app выполнив команду python manage.py startapp api_app.

Переходим сразу в settings.py и добавляем новое приложение в INSTALLED_APPS.

И создадим файл urls.py в директории приложения api_app.


Перенос моделей и файлов.
Далее необходимо перенести всё связанное с API из приложения blog в приложение api_app.

Начнём с модели. Полностью вырезаем код необходимых моделей и переносим его в файл models.py в новом приложении.
Проверяем, не осталось ли каких-либо связей в других моделях относительно перенесённых. Если остались, правим импорты.

Затем в класс Meta перенесённых моделей добавляем новый пункт app_label и указываем имя старого приложения.
Таким образом код у нас находится в новом приложении, но модель считается моделью старого приложения и таблицы БД не будут перезаписаны. Если у вас нет данных в БД, пометку делать не нужно.

Далее переносим из файла admin.py связанные классы. Правим импорты.

Переносим файлы api.py и serializers.py. Правим импорты.

Переносим URL-паттерны для API из файла urls.py в новое приложение. Правим импорты и переходим в файл urls.py, находящийся в директории проекта. Добавляем новый паттерн path('api/', include('api_app.urls', namespace='api_app')),.

Применяем миграции. Результатом должно быть отсутствие новых миграций.

Готово. Всё должно работать, как и раньше.

Завершение.
Да, модели хоть и находятся на новом месте, но относятся к старому. Возможно в будущем, углубившись в Django я найду решение с "безболезненным" переносом данных модели. Но пока оставлю так, как говорится "надо было думать раньше".

Возможно, в моём коде есть ещё ошибки которые я не замечаю, буду рад если укажете на них в комментариях.

Файлы к посту, можно получить в боте по коду: 919633
Пост на сайте.
Поддержать канал.
🔥2
Cpp FreeGPT WebUI - Бесплатный GPT на вашем сервере

FreeGPT мёртв, да здравствует FreeGPT!

Сегодня, благодаря комментариям на канале, выяснилось, что FreeGPT, о котором я писал ранее прекращает свою работу.
Добавил об этом пометку и сообщение автора в посте "Бесплатный Chat-GPT4 на вашем ПК или сервере".

Заинтересовался вопросом, нет ли альтернатив и, как оказалось альтернатива есть.

Встречайте Cpp FreeGPT WebUI.

Написан на C++. Использует GPT4Free API и WebUI от ChatGPT Clone.

Доступны вариации моделей Chat GPT 3.5 Turbo и Chat GPT 4 (насчёт четвёрки не уверен, скорее всего та же тройка).

Ссылка на GitHub проекта: https://github.com/fantasy-peak/cpp-freegpt-webui#cpp-freegpt-webui

Единственный минус данного решения - установка либо на Linux, либо в Docker-контейнере.

А остальное, вы все прекрасно знаете. Приступим к установке.
Установка на Linux.

1. Клонируем репозиторий выполнив команду:
git clone https://github.com/fantasy-peak/cpp-freegpt-webui.git


2. Проверьте установленную версию пакета g++. Необходима версия g++ >= 13.1.0 (GCC).
3. Установите xmake, выполнив следующие команды:
wget https://github.com/xmake-io/xmake/releases/download/v2.8.2/xmake-v2.8.2.xz.run
chmod 777 xmake-v2.8.2.xz.run
./xmake-v2.8.2.xz.run
source ~/.xmake/profile


4. Установите libcurl-impersonate:
# ubuntu
apt-get install libcurl4-openssl-dev

# centos7
yum install libcurl-devel.x86_64

wget https://github.com/lwthiker/curl-impersonate/releases/download/v0.5.4/libcurl-impersonate-v0.5.4.x86_64-linux-gnu.tar.gz
sudo mv libcurl-impersonate-v0.5.4.x86_64-linux-gnu.tar.gz /usr/lib64
cd /usr/lib64
sudo tar -xvf libcurl-impersonate-v0.5.4.x86_64-linux-gnu.tar.gz
export LD_LIBRARY_PATH=/usr/lib64:$LD_LIBRARY_PATH
export LIBRARY_PATH=/usr/lib64:$LIBRARY_PATH


5. Компиляция:
git clone https://github.com/fantasy-peak/cpp-freegpt-webui.git
cd cpp-freegpt-webui
xmake build -v -y
xmake install -o .
cd bin
./cpp-freegpt-webui ../cfg/cpp-free-gpt.yml


6. После всех манипуляций, чат будет доступен в браузере по ссылке: https://127.0.0.1:8858/chat.


Запуск в Docker-контейнере.

1. Скачиваем актуальную версию образа, выполнив команду:
docker pull fantasypeak/freegpt:latest


2. Запускаем контейнер:
docker run -p 8858:8858 --name freegpt -d fantasypeak/freegpt:latest


3. Чат будет доступен в браузере по ссылке: https://127.0.0.1:8858/chat.

Завершение.
Запускал в Docker-контейнере на Windows и VPS-сервере. Работает без нареканий

Пост на сайте.
Поддержать канал.
🔥2
AIOgram3 12. Добавление бота в чат

Telegram-боты могут работать не только 1 на 1 с пользователем, но и с целыми чатами.

Вот и в чате канала появилась необходимость в боте, а именно в следующем функционале:
1. Приветствие новых пользователей чата.
2. Удаление сообщений, содержащих мат (уж простите, но чат публичное место и если без мата не обойтись, то лучше промолчать).
3. Погода. Да, погода. Уж не знаю на кой ляд она сдалась, но раз просят - надо делать.

И, что самое удобное, бот будет действовать на комментарии к постам на канале.
Добавление бота в чат.
Начнём с самого начала, а именно с добавления бота в чат.

В чате с ботом открываем его профиль.
Там выбираем пункт "Добавить в группу". В открывшемся списке выбираем необходимую группу.

Бот добавлен, но он не имеет доступа к сообщениям. Он будет работать, если к нему напрямую обращаться по команде, а остальные сообщения он не будет видеть. Это не позволит ему проверять сообщения, а также не позволит работать машине состояний, если будет несколько уровней взаимодействия.

Чтобы разрешить доступ к сообщениям боту, необходимо сделать его администратором чата. Достаточно минимальных прав, либо можно на будущее выбрать необходимое. Всё зависит от требуемого функционала.
Для этого откройте управление группой и выберите пункт "Администраторы". Нажмите "Добавить администратора" и выберите в списке вашего бота.
Затем выберите необходимые права и по желанию боту можно прописать роль.

Готово. Бот добавлен в группу и может читать сообщения.


Получение идентификатора чата.
Для взаимодействия бота с чатом, разграничением функционала и корректной работы, необходимо знать ID-чата.

Есть разные способы получить идентификатор, от простого - воспользоваться другим ботом, до замороченного - вывод идентификатора в консоль при срабатывании команды.

Воспользуемся простым.

Открываем чат с ботом https://t.iss.one/userinfobot
Сразу после запуска он покажет ваш Telegram-ID, но нам необходим ID-чата.

Переходим в чат и пишем сообщение от имени чата. Затем выделяем сообщение и пересылаем его в ранее упомянутого бота.

В ответ он пришлёт идентификатор чата начинающийся со знака -. Всё верно, Идентификаторы пользователей - целое число, а идентификаторы чатов и каналов - отрицательное.

Отлично. Теперь у вас есть идентификатор вашего чата.


Доработка кода бота.
Разобравшись с формальностями, перейдём к коду. Откроем файл settings.py.

У нас есть Dataclass Secrests в котором мы храним важные для работы бота данные, а именно токен и идентификатор администратора.

Добавим новую строчку:
group_id: int = -12345


Вместо -12345 вставляем идентификатор вашего чата.


Меню для чата.
На данный момент в моём боте есть четыре команды:
- start - Начало работы
- get_file - Получение файла с материалами
- help - Помощь по доступным командам
- about - Информация о боте

Однако, в чате в первых двух командах нет необходимости. Я хочу, чтобы они были доступны только из бота и их не было видно в меню чата.

Для этого откроем файл commands.py.
В теле функции set_commands находится список commands, определяющий меню бота. Его мы не трогаем.
Ниже до команды await bot.set_my_commands создаём новый список commands_chat и вписываем туда, по аналогии со списком выше, команды доступные в чате.

У меня на данный момент это две команды:
- help - Помощь по доступным командам
- about - Информация о боте

Ниже, под await bot.set_my_commands добавляем ещё одну такую же строчку. Изменив первый аргумент на новый список, а во втором прописав BotCommandScopeChat(chat_id=Secrets.group_id). Так мы определяем, что меню будет доступно в конкретном чате.

Новая строка:
from botlogic.settings import Secrets

await bot.set_my_commands(commands_chat, BotCommandScopeChat(chat_id=Secrets.group_id))


Теперь в чате будут отображаться только необходимые команды.

Ограничение существующих команд.
Теперь необходимо ограничить существующие команды от выполнения в чате. Это необходимо сделать, поскольку команду можно написать и не открывая меню и если команда там не указана, не значит, что она не может быть выполнена.

Откроем файл send_file.py и в самое начало функции send_file_start добавим условие:
from botlogic.settings import Secrets

if message.chat.id != Secrets.group_id:


Получится вот так:
async def send_file_start(message: Message, state: FSMContext):  
if message.chat.id != Secrets.group_id:
await message.answer(views.send_file_start_msg())
await state.set_state(SendFileSteps.get_code_from_user)


Во второй функции нет необходимости в проверке, поскольку в неё не попасть без первой.
Далее откроем файл simple.py и добавим такое же условие к функции start_command.

Функция about_command у меня универсальна, оставляем как есть.

Функция help_command выводит текст со списком доступных команд, следовательно, необходимо сделать две текстовые функции в файле views.py.
Сперва добавим условие, как и раньше, ниже прописываем else и повторяем команду с сообщением, изменив название функции, например, на help_chat_message.

Получается вот так:
async def help_command(message: Message):  
if message.chat.id != Secrets.group_id:
await message.answer(views.help_message())
else:
await message.answer(views.help_chat_message())


Переходим во views.py и создаём функцию, возвращающую текст с новым списком команд.


Завершение.
Готово. Бот подготовлен к работе в чате. В следующем посте начнём делать самую востребованную функцию.
Всё правильно, это погода.

Файлы к посту, можно получить в боте по коду: 385736
Пост на сайте.
Поддержать канал.
Классика..
👍2😱1
Акция! Только сегодня успейте записаться по ссыл....
👍5
AIOgram3 13. Прогноз погоды в боте - OpenWeatherMap

Самое востребованное в чате и наверное, одно из самых простых применений бота - прогноз погоды.

Использовать будем сервис OpenWeatherMap.

На бесплатном тарифе нам доступно 60 запросов в минуту и 1000 в день.


Регистрация и получение API-ключа.
Начнём с регистрации и получение ключа для API. После его получения, он начинает действовать в течении "пары часов", как раз успеем написать код.

Переходим на сайт: https://home.openweathermap.org/users/sign_in
Нажимаем на "Create an Account."

Вводим ник, почту, два раза пароль и отмечаем две первые галочки. Другие три галочки не трогаем, если конечно вы не желаете получать спам. Проходим капчу и нажимаем "Create Account".

Далее нас спросят с какой мы целью тут. Я выбрал "Education/Science". И ведь не соврал 😉. Нажимаем "Save".

Далее необходимо подтвердить электронную почту.
👍1
Сверху-справа нажимаем на свой ник, в выпадающем списке выбираем пункт "My API keys". Оказываемся на странице, где уже есть один ключ. Также после подтверждения почты, ключ и сопутствующая информация придёт на почту.

Как я и упомянул ранее, он активируется не сразу, а спустя какое-то время, поэтому переходим к коду.


Добавляем ключ.
Для начала, добавим API-ключ в наши "секреты".

Откроем файл settings.py и добавим новую строку в классе Secrets:
weather_key: str = 'ваш ключ'



Получение погоды.
В пакете utils создадим новый файл get_weather.py.

Создаём функцию request_weather, принимающую один аргумент city.

Код функции:
import requests  

from botlogic.settings import Secrets


def request_weather(city):
result = requests.get("https://api.openweathermap.org/data/2.5/find",
params={
'q': city,
'type': 'like',
'units': 'metric',
'lang': 'ru',
'APPID': Secrets.weather_key,
}).json()
if result['count'] == 0:
return 0
else:
return generate_result(result, city)


В переменную result попадает JSON с ответом от сервера.
С помощью библиотеки requests делаем GET-запрос. Первым аргументом передаём API URL.
Вторым аргументом передаём словарь параметров, подробнее по пунктам:
- q - отправляемый запрос, в нашем случае это город.
- type - тип поиска, в нашем случае ищем по совпадению.
- units - система измерения, в нашем случае метрическая.
- lang - язык.
- APPID - API-ключ.

Далее идёт проверка, если в ответе count равен нулю, то либо пользователь ошибся в названии города, либо такого города нет в базе сервиса. Возвращаем 0.
Иначе, возвращаем результат выполнения функции генерирующей строку с результатом.


Парсинг ответа и генерация строки с результатом.
Под функцией request_weather создаём новую функцию generate_result, принимающую данные из сервиса data и город city.

Код функции:
def generate_result(data, city):
temp = int(data['list'][0]['main']['temp'])
feels_like = data['list'][0]['main']['feels_like']
pressure = int(data['list'][0]['main']['pressure']) * 0.75
humidity = data['list'][0]['main']['humidity']
wind_speed = int(data['list'][0]['wind']['speed'])
rain = 'не ожидается' if data['list'][0]['rain'] is None else 'ожидается'
snow = 'не ожидается' if data['list'][0]['snow'] is None else 'ожидается'
weather = data['list'][0]['weather'][0]['description']
return f'''
<b>Прогноз погоды в городе {city}</b>

Сейчас температура {temp}°C
Ощущается как {feels_like}°
⛅️{weather}⛅️
💨 Скорость ветра {wind_speed}м/с 💨
Давление {pressure} мм рт.ст.
Влажность {humidity}%
💦 Дождь {rain}
❄️ Снег {snow}
'''


Тут мы из ответа получаем значения в 8 переменных, затем формируем строку с результатом.

Строку и необходимые данные можно кастомизировать по желанию.


Команда получения погоды.
Теперь перейдём к созданию команды. В пакете handlers создадим файл weather_fsm.py.

Создадим асинхронную функцию get_weather_command, принимающую два аргумента: message и state.

В теле функции прописываем две команды:
- Отправка сообщения пользователю. В сообщении просим ввести город или отменить запрос.
- Переход на следующий шаг в машине состояний.

Код функции:
from aiogram.fsm.context import FSMContext  
from aiogram.types import Message

from botlogic import views
from botlogic.utils.statesform import GetWeatherSteps


async def get_weather_command(message: Message, state: FSMContext):
await message.answer(views.enter_city())
await state.set_state(GetWeatherSteps.BY_CITY)


Для первой команды необходимо открыть файл views.py и создать функцию enter_city, возвращающую строку с просьбой ввести город или отменить запрос.

Для второй команды необходимо открыть файл statesform.py и создать новый класс GetWeatherSteps:
class GetWeatherSteps(StatesGroup):  
BY_CITY = State()
👍1
Возвращаемся в файл weather_fsm.py и ниже создаём вторую функцию get_by_city.

Код функции:
from botlogic.utils.get_weather import request_weather
from botlogic.settings import bot

async def get_by_city(message: Message, state: FSMContext):
if message.text.lower() == 'отмена':
await message.answer(views.abort_weather())
await state.clear()
else:
result = request_weather(message.text)
if result:
await message.reply(result)
await message.answer(views.weather_request_done())
await state.clear()
else:
await bot.send_message(message.chat.id, views.weather_wrong_city())
await state.set_state(GetWeatherSteps.BY_CITY)


Первым делом проверяем, отменил пользователь запрос или нет.
Если запрос отменён, выводим сообщение и очищаем состояние.

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

Далее снова проверка. Если вернулась сформированная строка, отвечаем пользователю сформированной строкой, затем отправляем сообщение, что задача выполнена и очищаем состояние.

Иначе, если вернулся 0, просим пользователя повторить попытку или отменить запрос.

Готово. Осталось только зарегистрировать команды и прописать в меню.


Регистрация команды.
Откроем файл main.py и найдем другие зарегистрированные команды.

Ниже добавим следующие строки:
from botlogic.handlers import weather_fsm
from botlogic.utils.statesform import GetWeatherSteps


dp.message.register(weather_fsm.get_weather_command, Command(commands='weather'))
dp.message.register(weather_fsm.get_by_city, GetWeatherSteps.BY_CITY)



Добавляем в меню.
Откройте файл commands.py в пакете utils.

Добавьте команду в меню, по аналогии с другими:
BotCommand(  
command='weather',
description='Получить прогноз погоды'
),



Завершение.
Поздравляю. Теперь ваш бот умеет сообщать прогноз погоды.
Как я и говорил в начале, добавление функционала погоды, достаточно простое. Также оно служит "примером" использования бота для получения каких-либо данных по API.

Файлы к посту, можно получить в боте по коду: 603735
Пост на сайте.
Поддержать канал.
🔥2
😐😐😐
🔥5
Состоялся релиз новой версии Python - 3.12.

Из ключевых изменений:
- Улучшена работа с f-строками.
- Улучшена поддержка многоядерных систем, за счёт определения добавления поддержки изолированных субинтерпретаторов и отдельных GIL.
- Улучшена работа с ошибками и подсказками для ошибок.

Подготовил для вас перевод ключевых изменений.

Пост на сайте.
Поддержать проект.
🔥7
Правительство РФ и США сегодня проведут проверку системы оповещения населения через мобильные телефоны.
А вы уже выключили мобилку и замотали её в фольгу?
Anonymous Poll
11%
А? ЧТО? ГДЕ МОЯ ФОЛЬГА?! *надевая шапочку*😱
4%
Пойду-ка я до магаза... Ковидные запасы гречки и туалетки кончаются😬
32%
Да всё нормуль будет, чипы от привики активируют и всё🙄
54%
О чём вы вообще?😁
🤔И где эта Света?
🤣3
Статистика сайта за 3 месяца

Месяц назад я закончил выкладывать на сайт вышедшие в Telegram-канале посты. На тот момент сайту было 2 месяца.

С того времени сайт начал появляться в выдаче поисковых систем и появились переходы из них. Переходы из поиска занимают третье место по числу пользователей.

На втором месте переходы из Telegram-канала. Тут всё понятно.

Удивительно, но на первом месте стоят переходы из Dzen. Я туда дублирую посты, статистика там неважная - практически нет подписчиков, дочитываний статей очень мало, кликов из ленты и того меньше. Тем не менее это на данный момент основной источник трафика на сайт.

В сухом итоге:
- Посетители: Было 136, стало 210. Прирост 54.41%.
- Переходы из Dzen: Было 15.9%, стало 37.6%. Прирост 136.47%.
- Переходы из Telegram-канала: Было 12.2%, стало 24.2%. Прирост 98.36%.
- Переходы из поиска: Было 0%, стало 17.6%.

Я считаю, это хороший результат. Пишите в комментариях, если интересно наблюдать статистику каждый месяц.

Пост на сайте.
Поддержать канал.
🔥8
Которых нет.
🔥4😢2