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

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

Реклама и взаимопиар: @Murzyev1995
Сотрудничество и др.: @proDreams
Download Telegram
Второй - сложность алгоритма и затрачиваемые ресурсы.
В используемом списке бранных слов насчитывается свыше 1000 слов.
Сообщение от пользователя очищается от пунктуации и разделяется на множество (set) слов.
При большом потоке сообщений, это может сильно увеличить нагрузку на сервер и замедлить работу бота.
Я опишу уже улучшенный вариант, к которому мы пришли с подписчиком @rusheslav после тестирования фильтра в чате, но если у вас есть комментарии или рекомендации по улучшению кода, буду рад!


Множество запрещённых слов.
Использовать будем готовый список: https://github.com/bars38/Russian_ban_words
Вы можете составить свой или найти другой.

Скачиваем файл words.txt. Для большей наглядности, я его переименовал в ban_words.txt.

В корне проекта создаём директорию res и помещаем туда файл.

Открываем файл settings.py и в начале, после импортов создаём константу BAN_WORDS.
В этой константе создаётся множество из слов в файле.
BAN_WORDS = set(line.strip() for line in open('res/ban_words.txt'))



Фильтрующая функция.
В пакете handlers создадим новый файл filter_words.py.

Создаём асинхронную функцию check_message, принимающую message.

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

from aiogram.types import Message

from botlogic import views
from botlogic.settings import BAN_WORDS, logger


async def check_message(message: Message):
contains_ban_word = False

if message.text:
message_words = set(message.text.translate(str.maketrans('', '', string.punctuation)).split())
filtered_message = message.text
for word in message_words:
if word.lower() in BAN_WORDS:
filtered_message = filtered_message.replace(word, "*" * len(word))
contains_ban_word = True

if contains_ban_word:
await message.delete()
logger.info(f"Удалено сообщение от пользователя {message.from_user.username}: {message.text}")
await message.answer_sticker('CAACAgIAAxkBAAEKbW1lGVW1I6zFVLyovwo2rSgIt1l35QADJQACYp0ISWYMy8-mubjIMAQ')
await message.answer(views.filtered_message(message.from_user.username, filtered_message))


По ходу действий:
Создаём переменную contains_ban_word. Это "флаг", по умолчанию считаем, что в сообщении нет запрещённых слов.
После проверки на наличие сообщения создаём переменную message_words, в которой создаём множество очищенных от пунктуации слов. Множество не позволяет хранить в себе два одинаковых объекта, тем самым в переменной будут только уникальные слова. И переменную filtered_message, в которую помещаем копию сообщения.

Далее проходимся циклом for по множеству слов из сообщения.
Если слово есть среди запрещённых, заменяем его на звёздочки в количестве длинны слова и выставляем "флаг" о наличии в тексте запрещённых слов.

Далее проверка "флага", если переменная contains_ban_word по-прежнему в изначальном состоянии False, то бот ничего не делает.
В противном случае бот сперва удаляет исходное сообщение. Записывает сообщение и информацию от кого в лог файл. Затем отправляет стикер по его идентификатору, после чего отправляет сообщение с исправленным текстом.


Как получить идентификатор стикера.
Для получения идентификатора стикера, достаточно отправить боту https://t.iss.one/idstickerbot стикер, идентификатор которого хотите получить.
В ответ он пришлёт идентификатор.
Готово.


Завершение
Осталось только зарегистрировать обработчик. Добавляем в файл main.py следующую строку: dp.message.register(check_message). Отсутствие второго аргумента означает, что бот будет обрабатывать все сообщения.

Готово. Теперь бот фильтрует нежелательные слова в сообщениях. Конечно это не панацея и мы все знаем, как можно исхитриться в Русском языке, но основную часть отсеивать будет. А потом просто добавляем слова.


Дополнительно: Изменение логгера.
Изначально логгер находился в файле main.py и не записывал никуда события, только отображал в терминале.

Вынес логгер в файл settings.py. Определил два обработчика, для терминала и для файла.
Код нового логгера:
# Логгер  
logger = logging.getLogger()
logger.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s - [%(levelname)s] - %(name)s - (%(filename)s).%(funcName)s(%(lineno)d) - %(message)s")

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)

file_handler = logging.FileHandler("logs.txt")
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)

logger.addHandler(console_handler)
logger.addHandler(file_handler)


Файлы к посту, можно получить в боте по коду: 702209
Пост на сайте.
Поддержать канал.
Кто помнит карточки?))
👍11
Соберём результаты опроса по возрасту подписчиков.

В опросе приняло участие 47 человек!

Получаются следующие цифры:
2 подписчика до 15 лет.
На третьем месте подписчики от 19 до 25 лет - в числе 8 проголосовавших.
Бо́льшая часть, а именно 19 человек в возрасте от 26 до 35.
Не отстают и подписчики от 36 до 45, их насчитывается 16.
Один подписчик в возрасте 46-55.
И одна(ин) стеснительный, пожелавший остаться неизвестным)

В комментарии к опросу пришло предложение сделать опрос по городам подписчиков.
Вместо опроса, пишите в комментариях из какого вы города! Потом также оформлю результаты.
👀
😁4🔥1
Приветствую.

В комментариях к шуточному опросу про тестирование систем оповещения, зашла речь о бункерах. И мне вспомнился один хороший фильм на эту тему.

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

Фильм на сегодня: Взрыв из прошлого (1998).

Краткий синопсис: Адам Уэббер родился нормальным ребенком, но, по глупому недоразумению, провел 35 лет в бомбоубежище вместе со своими родителями. Наивный и искренний, он впервые оказывается среди людей в бушующем Лос-Анджелесе 90-х! И кто знает - может, он еще успеет наверстать упущенное?!

Посмотреть фильм можно по ссылке: https://www.sspoisk.ru/film/2408

Может быть у вас есть свой вариант фильма на вечер? Делитесь им в комментариях!
AIOgram3 15. Обработка события вступления или покидания чата

Последняя (на данный момент) задача - это приветствие нового участника чата и оповещение о том, что кто-то нас покинул.

Решение достаточно простое и пост будет коротким.
Обработка событий.
Откроем файл events.py в пакете handlers и создадим две асинхронные функции: on_user_join и on_user_left.
Обе функции принимают event - объект класса ChatMemberUpdated.

В теле функции напишем отправку сообщения в ответ на событие, обращаясь к методам new_chat_member и old_chat_member.

Код функций:
from aiogram.types import ChatMemberUpdated


async def on_user_join(event: ChatMemberUpdated):
await event.answer(views.join_message(event.new_chat_member.user.first_name))


async def on_user_left(event: ChatMemberUpdated):
await event.answer(views.left_message(event.old_chat_member.user.first_name))


Не забудьте написать функции, возвращающие текст сообщения в файле views.py.


Регистрация обработчиков.
Переходим в файл main.py и добавляем следующие строки:
from aiogram.filters import ChatMemberUpdatedFilter, IS_NOT_MEMBER, IS_MEMBER


dp.chat_member.register(on_user_join, ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER))
dp.chat_member.register(on_user_left, ChatMemberUpdatedFilter(IS_MEMBER >> IS_NOT_MEMBER))

Готово! Теперь если новый пользователь зайдёт в чат, его поприветствует бот, а если кто-то его покинет, бот уведомит и об этом.

Файлы к посту, можно получить в боте по коду: 164296
Пост на сайте.
Поддержать канал.

#aiogram #гайды #python
🔥1
😐И так каждый раз...
😭3😁1
👀
👍6😁1
Годовой курс длиной в ~16 месяцев, а результат тот же...
🔥8💯1
Django 31. Форма авторизации и кнопка выхода

Продолжаем развивать проект.

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

И начнём мы с создания нового приложения.


Приложение user_app.
В прошлом посте я упоминал о важности разделения разных логических блоков на Django-приложения. Поэтому для пользователей сделаем отдельное приложение user_app.

Для создания нового приложения выполните в терминале следующую команду:
python manage.py startapp user_app


Создастся новая директория с основными файлами.

Сразу откроем файл settings.py и добавим новое приложение в список INSTALLED_APPS - 'user_app.apps.UserAppConfig',
Авторизация.

Форма авторизации.
Создадим форму авторизации.

На самом деле, делать этого не обязательно.
Можно воспользоваться встроенной формой, собственно от которой будем наследоваться, однако тут встаёт вопрос с её отображением. У стандартной формы отсутствуют стили.
Можно конечно их прописать с помощью CSS-селекторов, но у меня в данный момент используется Bootstrap5.
Для того чтобы воспользоваться его возможностями, необходимо форме определить конкретные стили, а для этого нужно её переопределить.

В директории приложения user_app создадим новый файл forms.py.

Создадим класс LoginForm, унаследованный от AuthenticationForm.

Затем создадим два поля:

Первое будет username. В нём определим поле символов.
Первым атрибутом будет максимальная длина имени пользователя. По умолчанию в модели пользователя максимальная длина составляет 150 символов, укажем столько же или можете по своему усмотрению указать меньше.
Вторым будет название поля.
Третьим атрибутом будет виджет, в котором определим текстовое поле ввода. Внутри в качестве атрибутов присвоим полю класс, а также укажем текст внутри поля.

Второе поле password. Также является полем символов.
Первый атрибут такой же - максимальная длина, которая равна 128 символам.
Второй - название поля.
Третьим атрибутом определим виджет, в этот раз это будет поле ввода пароля. Точно также передаём в атрибутах класс и текст внутри поля.

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

Код:
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User


class LoginForm(AuthenticationForm):
username = forms.CharField(
max_length=150,
label='Имя пользователя',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Введите имя пользователя'
})
)
password = forms.CharField(
max_length=128,
label='Пароль',
widget=forms.PasswordInput(attrs={
'class': 'form-control',
'placeholder': 'Введите пароль'
})
)

class Meta:
model = User
fields = ['username', 'password']



Представление страницы авторизации.
Откроем файл views.py.

Напишем класс CustomLoginView, унаследованный от LoginView.

В классе пропишем всего три поля:
- authentication_form - в этом поле указываем класс нашей переопределённой формы.
- template_name - в этом поле указываем HTML-файл шаблона.
- extra_context - в этом поле указываем дополнительные переменные, передаваемые в шаблон.

Также переопределим метод get_success_url. В этом методе, воспользовавшись функцией reverse_lazy указываем на какую страницу перейдёт пользователь после авторизации. Пока у нас нет страницы профиля, будем перенаправлять пользователя на главную страницу.

Код:
from django.contrib.auth.views import LoginView
from django.urls import reverse_lazy

from user_app.forms import LoginForm


class CustomLoginView(LoginView):
authentication_form = LoginForm
template_name = 'user_app/login.html'
extra_context = {'title': 'Авторизация на сайте'}

def get_success_url(self):
return reverse_lazy('blog:index')
👍1
Шаблон страницы авторизации.
В директории templates создадим новую директорию user_app, а в ней файл login.html.

В этом файле прописываем тег формы.
Внутри прописываем:
- Тег csrf_token. Данный тег обеспечивает защиту формы.
- Проверку на наличие не связанных с полями ошибок. Если ошибки есть, выводим их.
- В цикле for перебираем поля формы. Выводим название поля и само поле.
- Кнопку для отправки формы.

Код:
<form method="post">  
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger">
{{ form.non_field_errors }}
</div>
{% endif %}
{% for field in form %}
<p class="form-label">{{ field.label }}</p>
<p>{{ field }}</p>
{% endfor %}
<button type="submit" class="btn btn-outline-success">Войти</button>
</form>



URL-паттерн страницы входа.
Создадим новый файл urls.py.

Указываем имя приложения и в списке urlpatterns добавляем путь для страницы авторизации.

Код:
from django.urls import path  

from user_app import views

app_name = 'user_app'
urlpatterns = [
path('login/', views.CustomLoginView.as_view(), name='login'),
]


Теперь необходимо добавить приложение в основной файл. Откроем файл urls.py в директории проекта и добавим новую строку:
path('user/', include('user_app.urls', namespace='user_app')),


Все страницы связанные с пользователем будут начинаться на user.
Например:
- user/login - авторизация
- user/signup - регистрация
- user/<username> - профиль
- И так далее....


Выход.

Выход с сайта - очень простой функционал, но есть два варианта его исполнения:
1. Если вам не нужна промежуточная страница.
2. Если вам нужна промежуточная страница.

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

Рассмотрим оба варианта.

Откроем файл urls.py в директории приложения. Добавим в список urlpatterns следующую строку и определим новый импорт:
from django.contrib.auth import views as auth_views  

path('logout/', auth_views.LogoutView.as_view(), name='logout'),


Для выхода используем встроенное в Django представление. Определяем URL и название.

На этом месте начинаются различия между первым и вторым вариантом.

Для первого варианта достаточно открыть файл settings.py и определить новый параметр LOGOUT_REDIRECT_URL, прописав в нём прямой путь или имя URL-паттерна.

Пример для главной страницы:
LOGOUT_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = 'blog:index'


Для второго варианта необходимо в URL-паттерне внутри представления добавить путь к шаблону:
path('logout/', auth_views.LogoutView.as_view(template_name='user_app/logout.html'), name='logout'),


Затем создать этот шаблон с необходимым вам содержимым, например:
{% extends 'blog/base.html' %}  

{% block content %}
<div class="form-section container">
<h2>Вы успешно вышли с сайта</h2>
<a href="{% url 'user_app:login' %}">Войти в личный кабинет снова</a>
</div>{% endblock %}


Готово.

Кнопка входа и выхода в шапке сайта.
Остался последний "штрих" - добавить кнопку для входа и выхода в шапку сайта.

При этом, кнопка входа должна быть видна только неавторизованному пользователю, а кнопка выхода авторизованному соответственно.
🔥1
Откроем файл header.html и в удобное место добавим следующий код:
{% if user.is_authenticated %}
<div class="dropdown">
<button class="btn btn-primary my-btn dropdown-toggle me-3" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
{{ request.user }}
</button>
<ul class="dropdown-menu dd-color">
<li><a class="dropdown-item dd-item-color" href="{% url 'user_app:logout' %}">Выйти</a></li>
</ul>
</div>
{% else %}
<li class="nav-item">
<a class="nav-link btn btn-primary my-btn me-3"
href="{% url 'user_app:login' %}">Войти</a>
</li>
{% endif %}

Можно запустить Django и протестировать возможность входа и выхода с сайта.

Файлы к посту, можно получить в боте по коду: 896551
Пост на сайте.
Поддержать канал.
🔥2
(╯ ° □ °) ╯ (┻━┻)
👀👀👀
😁7
И ведь работает уточка)
😁2
Приветствую.

На одном из проходимых мной курсов, итоговым заданием необходимо было сделать прототип сервиса по сокращению ссылок.
Задание достаточно простое и мне стало интересно, а что если вынести фронт на Vue, а бэк пусть остаётся на Django?

Также, дополнительным плюсом будет иметь свой сервис сокращения ссылок, поскольку часто ссылка + UTF метки занимают много места, а сервисы типа click.ru|bit.ly|vk.cc и прочие, помечаются как небезопасные.

Результатом стал сайт https://lkeep.ru/

Работать будет так: https://lkeep.ru/l/EMDRTbZUpi

На данный момент из "косяков" только отсутствие адаптивной вёрстки и неработающий(недописанный) функционал копирования ссылки в лк.

Мне нужны ваши отзывы. Как вам, может что-то улучшить/изменить? Пишите в комментариях!

P.S. Регистрация обязательна, иначе это будет такая же помойка как и другие сайты.
Django 32. Форма регистрации

Авторизацию мы добавили. Теперь добавим возможность регистрироваться на сайте.

В целом, процесс примерно такой же как и с авторизацией.

Продолжаем работать в директории приложения user_app.
Почти весь пост будем использовать, написанный в прошлом посте код с небольшими изменениями.


Форма регистрации.
Откроем файл forms.py и создадим новый класс RegistrationForm, унаследованный от UserCreationForm.

Далее добавляем 4 поля: username, email, password1 и password2.

Поля username и password можно скопировать из формы авторизации. Они такие же. Разве, что в поле password2 изменим название и описание поля.
Поле email описывается похожим образом, но для валидации используется EmailInput.

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