Код на салфетке
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
Django + AIOgram3 + Redis - Отправка поста с Django в AIOgram
Автор: Иван Ашихмин

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

Решение было найдено не сразу. Вариантов реализации отправки данных из Django в AIOgram3 достаточно много, я остановился на связке Django + AIOgram3 + Redis. И в этом посте расскажу, как можно реализовать такой функционал.
Напоминаю, на нашем канале проводится мероприятие по типу "Тайного Санты".

Нас уже 8 участников!

Пост тут: https://t.iss.one/press_any_button/263

Форма для участия тут: https://forms.gle/adM65uCYJEPgSggW7
🔥1
👀Зачем вообще надевать что-то на онлайн собес?
😁5
🔥4
☝️Всё правильно говорит)
😁7
Приветствую!

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


Оглавления:
Для удобства навигации есть посты с оглавлениями по темам:

"Сайт на Django"

"Telegram-бот на AIOgram3"

"Применение Docker"

"Полезные инструменты"

"Код в мешке"


Ресурсы канала:

Уютный и немного безумный чат канала.

Бот с материалами к постам

Сайт со всеми постами


Поддержка.

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

Также поддержать канал можно на Boosty.

Или внеся сайт в исключения вашего блокировщика рекламы.
🔥4
== != is
Автор: Андрей Лебедев


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

Итак, вспомним, что у нас есть:
*️⃣ Переменная - она же бирка на коробочке
*️⃣ Коробочка - она же ячейка памяти
*️⃣ Заводской номер коробочки - он же адрес ячейки памяти
*️⃣ Содержимое коробки - значение
*️⃣ Склад - память в целом.

Вспомнили? Отлично. Есть шанс, что если с разработкой не срастётся, всегда можно будет устроиться работать на склад. Шутка, всё обязательно получится, для этого мы здесь.
2🔥1
Оператор "=="
Итак, для чего нужен оператор "=="? Он позволяет проверить, совпадает ли содержимое двух коробок на складе (значения, хранящиеся в двух ячейках памяти). Он похож на работника склада, который умеет выполнять только эту задачу и никакую другую. Негусто, конечно, но зато справляется он со своей функцией на ура.

Как поставить работнику задачу? Дать ему данные с двух бирок (переменные) и попросить его сравнить содержимое коробок, на которых они наклеены. Рассмотрим на примере.


name = "Стэн"
same_name = "Стэн"

print(name == same_name)


Наш трудяга стремглав летит проверять, что у нас лежит в коробках с бирками name и same_name. А лежит у нас и там, и там одинаковая строка "Стэн". Оператор радостно нам сообщает об этом: True!

Закрепим - оператор "==" сравнивает переменные по значению (содержимое двух коробок с бирками, которые мы ему передали).

Ключевое слово "is"
Помимо работника, у нас в команде есть ключевое слово "is" (пускай на этот раз будет сотрудница). Умеет опять-таки немного, но работу свою выполняет чётко. Ей мы также даём информацию о том, что написано на двух бирках (названия переменных), а она проверяет, наклеены ли они на одну и ту же коробку (ссылаются ли переменные на одну и ту же ячейку памяти).

Рассмотрим на примере.


name = “Стэн"
print(id(name))

same_name = “Стэн"
print(id(same_name))

print(name == same_name)
print(name is same_name)


Из поста про переменные мы помним, что две одинаковые строки, которые мы кладем в две разные переменные, на самом деле будут лежать в одной и той же ячейке памяти, что и видно по адресу, который мы в обоих случаях вывели в консоль print’ом. И нашей сотруднице это видно, о чём она нам и заявляет: True.

То есть ещё раз: оператор "==" показывает, одинаковое ли значение (содержимое) хранится в двух ячейках памяти (коробках), на которые ссылаются две переменные (наклеены две бирки). Ключевое слово "is" показывает, ссылаются ли две переменные (наклеены ли две бирки) на одну и ту же ячейку памяти (коробку), или нет.

А зачем это вообще нужно?
Разве бывает так, чтобы ответы наших работников по поводу одинакового содержимого двух разных переменных различались?

Бывает. Рассмотрим на примере:


a = "Стэн"
b = "Стэн"
c = "Ст" + "эн"
d = input('Введите слово "Стэн": ') # Введем слово "Стэн" (без кавычек)

print(id(a)) # 4352025456
print(id(b)) # 4352025456
print(id(c)) # 4352025456
print(id(d)) # 4353014544

print(a == b) # True
print(a is b) # True

print(a == c) # True
print(a is c) # True

print(a == d) # True
print(a is d) # False


Несложно заметить, что во всех четырёх наших переменных в итоге окажется одна и та же строка (если мы честно ввели слово “Стэн” без кавычек, когда нас об этом попросили). Но в первых трех случаях эта строка оказалась в одной и той же ячейке, на которую “наклеились” три переменные: a, b, c.

Почему же в четвертом случае (для переменной d) потребовалось новая ячейка в памяти? Всё дело в том, как Python пытается экономить память. На этапе компиляции кода он способен сравнивать строки между собой, а также проверять результат простых операций со строками (вроде конкатенации строк “Ст” и “эн”). Так как этот результат соответствует уже существующей в памяти строке “Стэн”, нам незачем создавать еще один такой же объект. Смело клеим бирку на уже созданную ранее коробку со Стэном. Процесс оптимизации работы со строками и некоторыми другими объектами называется “интернированием”, и мы когда-нибудь уделим ему отдельное внимание.
А вот то, что мы вводим с клавиатуры и передаем в переменную d, попадает в память уже в момент работы кода и поэтому для новой строки подбирается новая ячейка.

Советую не верить мне на слово, а «пощупать» всё в PyCharm самостоятельно.

Ещё один пример
🔥4👍1
list_one = [1, 3, 5]
list_two = [1, 3, 5]
list_three = list_one
list_four = list_one.copy()

print(id(list_one)) # 4509660416
print(id(list_two)) # 4511778176
print(id(list_three)) # 4509660416
print(id(list_four)) # 4510270528

print(list_one == list_two) # True
print(list_one is list_two) # False

print(list_one == list_three) # True
print(list_one is list_three) # True

print(list_one == list_four) # True
print(list_one is list_four) # False


Пройдёмся сверху вниз по коду примера:

1️⃣ Мы создали список из трех цифр и записали его в переменную list_one.
2️⃣ Далее создали точно такой же список и записали его в переменную list_two.
3️⃣ Затем на ячейку, к которой уже “прикреплена” переменная list_one, "вешаем" еще одну переменную list_three.
4️⃣ Наконец, в переменную list_four записываем результат копирования списка из list_one.

Первое, на что стоит обратить внимание, list_one и list_two записались в разные ячейки памяти, что видно и по номерам этих ячеек, и по работе нашей прекрасной сотрудницы "is", которая выдает “False”, хотя её коллега "==" справедливо подтверждает, что содержатся в обеих переменных идентичные списки. Со строками из предыдущего примера в такой же ситуации результат, как мы помним, был другим. Да, такое поведение напрямую связано с особенностями этих типов данных, о чем мы подробно поговорим в следующий раз.

Второе, на что стоит обратить внимание, list_one и list_three “прицепились” к одной и той же ячейке. Это видно по работе функции id(), а также по показаниям наших сотрудников - "==" и "is".

Третий важный момент: когда мы используем функцию copy() для копирования списка, в переменную list_four попадает идентичный список с числами. Но записывается он уже в новую ячейку памяти, что легко видно по результатам работы id(), "==" и "is". Возможно, вы спросите, для чего вообще нужна эта манипуляция c copy(), если можно было бы, как и в случае с list_two, просто вручную пересоздать точно такой же список. Ну, хорошо, когда это можно сделать “на берегу” до запуска кода. А если список попадает к нам каким-то другим образом? Например, вводится через консоль или получается автоматически с какого-нибудь сайта? Если нам нужна точная его копия в другой ячейке памяти (а иногда это просто-таки необходимо), то copy() - один из возможных выходов из ситуации.

Ну и последнее

list_one = [1, 3, 5]
list_two = [1, 3, 5]
list_three = list_one
list_four = list_one.copy()

list_one.append(7)

print(list_one) # [1, 3, 5, 7]
print(list_two) # [1, 3, 5]
print(list_three) # [1, 3, 5, 7]
print(list_four) # [1, 3, 5]


Работаем всё с теми же четырьмя переменными, в которых находится всё то же самое. К списку в list_one добавляем еще одно число и выводим в консоль все четыре переменные. Для тех, кто внимательно следил за ходом нашей мысли, не должно стать сюрпризом, что изменения, помимо списка в переменной list_one, коснулись и списка в переменной list_three. Почему? Потому что это один и тот же список, который “лежит” в одной и той же коробке, к которой мы прицепили две бирки: list_one и list_three. Списки из list_two и list_four остались нетронутыми, потому что каждый из них спокойно себе лежит в своей “коробке”.

А вот если бы мы пытались проделать нечто подобное со строками или целыми числами, результат снова был бы отличным, но это уже совсем другая история…

Пост на сайте
Поддержать проект

#Python #код_в_мешке #переменные #управление_памятью #операторы #ключевые_слова #is #оператор_сравнения
🔥5
Django 36. Добавление постов пользователем
Автор: Иван Ашихмин

Продолжаем развивать наш блог. Мы с вами реализовали регистрацию и создали профиль пользователя, теперь реализуем добавление постов не только Администратором, но и Авторами.

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

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


Библиотека pytils.
В модели поста есть поле slug. Подробнее о нём я писал в постах "Django 16. Модель категорий" и "Django 20. Модель поста".
Когда мы добавляем пост через панель администратора, его заголовок преобразуется в текст на латинице автоматически.
В случае с добавлением поста на стороне сайта такой возможности нет, поэтому мы прибегнем к помощи библиотеки pytils, в которой есть функция slugify, переводящая текст из кириллицы в латиницу.

Установим библиотеку командой:
pip install pytils


И добавим её в requirements.txt:
pytils~=0.4.1



Группа пользователей "Автор".
Для того, чтобы создавать посты могли только авторы, а не все пользователи, необходимо создать такую группу в панели администратора.

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

Нажимаем кнопку "Добавить".
В поле "Имя" вводим название группы, в нашем случае - "Автор".

На данный момент никакие права не выдаём, нам нужна только группа пользователей.

Нажимаем "Сохранить".

Готово. Теперь у нас есть группа "Авторы".

Для добавления пользователя в эту группу необходимо открыть раздел "Пользователи", выбрать нужного пользователя и в блоке с выбором группы добавить группу "Автор" в выбранные.


Форма добавления поста.
Нам нужно создать форму модели для добавления нового поста.

Откроем файл forms.py в приложении user_app. Создадим новый класс AddPostByAuthorForm, унаследованный от forms.ModelForm.

Ранее мы прописывали каждое поле с его типом и виджетом. В этот раз мы поступим иначе: получим поля из модели и в конструкторе класса __init__ проитерируемся по списку полей, добавляя к атрибутам поля нужный bootstrap-класс.

После цикла, дополнительно пропишем полю "категория" ещё один класс для выпадающих списков.

Для тех, кто использует django-ckeditor5:
С использованием данного редактора вне панели администратора есть забавная особенность.

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

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

И прописываем стандартный внутренний класс Meta с указанием на модель и перечислением полей.


Код формы:
from blog.models import PostModel


class AddPostByAuthorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs.update({'class': 'form-control', 'autofocus': ''})

self.fields['short_body'].widget.attrs.update({'class': 'django_ckeditor_5'})
self.fields['full_body'].widget.attrs.update({'class': 'django_ckeditor_5'})
self.fields['category'].widget.attrs.update({'class': 'form-select'})
self.fields["short_body"].required = False
self.fields["full_body"].required = False

class Meta:
model = PostModel
fields = ('title', 'image', 'category', 'short_body', 'full_body', 'status', 'telegram_link',
'file', 'tags', 'telegram_body')



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

Создадим класс AddPostByAuthorView, унаследованный от двух классов: UserPassesTestMixin и CreateView.
UserPassesTestMixin - это класс в Django, предоставляющий удобный способ ограничения доступа к представлениям на основе пользовательских тестов. Он позволяет определить метод test_func, который выполняет проверку и возвращает True, если пользователь должен иметь доступ, или False, если доступ следует отклонить. Если пользователь не проходит тест, то он перенаправляется на страницу, указанную в login_url.

В классе пропишем три знакомых нам поля: template_name, form_class и model.

Также три метода:

test_func - в этом методе мы проверяем, что пользователь состоит в группе "Автор". Если пользователь в ней не состоит, то страница добавления поста не откроется.
get_success_url - указываем на какую страницу перенаправить пользователя после добавления поста.
form_valid - метод, проверяющий валидность формы. В нашем случае мы в ней заполняем два отсутствующих в форме поля: author и slug.


Код представления:
from django.contrib.auth.mixins import UserPassesTestMixin
from django.views.generic import CreateView
from pytils.translit import slugify

from blog.models import PostModel
from user_app import forms


class AddPostByAuthorView(UserPassesTestMixin, CreateView):
template_name = 'user_app/add_post.html'
form_class = forms.AddPostByAuthorForm
model = PostModel

def test_func(self):
return self.request.user.groups.filter(name='Автор').exists()

def get_success_url(self):
return reverse('user_app:user_profile', kwargs={'username': self.request.user.username})

def form_valid(self, form):
form.instance.slug = slugify(form.instance.title)
form.instance.author = self.request.user
return super().form_valid(form)



URL-паттерн страницы добавления поста.
Откроем файл urls.py и в список urlpatterns добавим следующую строку:
path('post/add_post/', views.AddPostByAuthorView.as_view(), name='add_post'),



Кнопка перехода на страницу добавления поста.
Я решил расположить кнопку в меню пользователя в шапке сайта.

Откроем файл header.html и в блоке авторизованного пользователя добавим проверку на наличие в группе "Автор" и саму кнопку, если он там есть:
{% if "Автор" in user.groups.all.0.name %}
<li>
<a class="dropdown-item dd-item-color" href="{% url 'user_app:add_post' %}">Добавить
пост</a>
</li>
{% endif %}
Шаблон страницы добавления поста.
В директории с шаблонами приложения user_app создадим файл add_post.html.

В целом это обычная форма, за исключением одного момента.
Если вы используете django-ckeditor5, то помимо стандартных csrf_token и form.as_p, необходимо добавить form.media. Без этого редактор не будет отображаться.


Код шаблона:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}

{% block content %}
<div class="container mt-3 d-flex justify-content-center">
<div class="col-lg-8 col-sm-12">
<h2>Добавление поста</h2>
<form method="post" enctype="multipart/form-data" action="{% url 'user_app:add_post' %}">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit" class="btn btn-success mt-3">Сохранить</button>
</form>
</div>
</div>
{% endblock %}


Готово. Теперь авторы могут добавлять посты.
В следующем посте сделаем формы добавления категории и файлов.

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

Пост на сайте
Поддержать проект

#Python #Django #модели #формы #представления #гайды #slugify #pytils #группы_пользователей #ckeditor #django_ckeditor #slug
👀И там вас могут заставить работать.

Прям как в каком-то "Неком проекте")))
😁4
Маленькая предыстория...
Автор: Некий Вестник Сплетен

Большинство из нас - студенты различных образовательных онлайн-платформ, и все мы прекрасно знаем, как выглядят чаты групп, куда скидывают полностью весь поток обучающихся. Часть студентов начинает активно знакомиться и вступать в диалоги, а часть (и таких, как правило, подавляющее большинство) отсиживаются в сторонке, лишь изредка читая "простыню" текста и спама. Постепенно народ "растекается" по личкам, и лишь немногие объединяются в подгруппы "по интересам".
🔥3
Так произошло и с нашим учебным потоком. Сначала спонтанным образом появился чат "Исповедальня программистов", затем зародился канал "Код на салфетке" и связанный с ним чат "Кот на салфетке". Он объединил людей разного уровня подготовки и специализаций. Это удивительно и прекрасно, когда люди вот так вот самостоятельно объединяются. Порой можно обсудить насущное или просто пообщаться как "бабушки на лавочке"... В процессе одного такого разговора поступило предложение создать групповой проект. Изначально не было даже идеи, что бы это могло быть, поэтому проект с ходу окрестили “неким”.

Желающие (их и в этом чате предсказуемо оказалось немного) стали активно присоединяться, предлагать варианты, проявлять интерес. Обсудили, поговорили и.... затихли... до поры до времени!

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

С этого момента начался проект! Первые обсуждения, решения, созвоны... И абсолютно никакого понимания, что делать дальше. Но я искренне верю, что совместными усилиями мы реализуем-таки “Некий проект”!

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

Пост на сайте
Поддержать проект

#Кот_на_салфетке #Код_на_салфетке #Групповой_проект #Некий_проект
🔥5😱1💩1
Docker 6. Собственный GIT-сервис - Гид по быстрому запуску Gitea на вашем сервере!
Автор: Иван Ашихмин

В процессе командной работы над "Неким проектом" (о начале этой работы можно прочитать в посте "Маленькая предыстория...") мы столкнулись со знакомой многим проблемой - последствиями санкций. Выяснилось, что пользователям из некоторых регионов РФ недоступен GitLab, а GitHub не позволяет работать в приватных репозиториях.

Мы перебрали несколько альтернативных вариантов (о чем вы узнаете в постах Некоего Вестника Сплетен) и в результате решили запустить свой собственный GIT-сервер.

Выбор пал на готовое решение Gitea, отличающееся от аналогов легковесностью, а также доступностью привычного функционала GitHub.
Установка будет производиться на VPS-сервере с Debian 12. Как установить Docker я рассказывал в посте "Docker 2.2 Установка Docker на Linux".

Официальная документация Gitea доступна по ссылке: https://docs.gitea.com/


Подготовка Docker-compose файла.
Подключитесь по SSH к вашему серверу и перейдите в рабочую директорию.
Затем выполните следующие команды для создания директорий и yaml-файла:
mkdir -p gitea/{data,config}  
cd gitea
touch docker-compose.yml


Выполним команду nano docker-compose.yml для открытия файла и напишем следующую конфигурацию:
Обратите внимание на отступы и форматирование!
version: '3'

services:
db:
image: postgres
restart: always
environment:
- POSTGRES_USER=db_username
- POSTGRES_PASSWORD=db_password
- POSTGRES_DB=db_name
- PGDATA=/var/lib/postgresql/data/pgdata
ports:
- "5432:5432"
volumes:
- ./postgres-db:/var/lib/postgresql/data

git:
image: gitea/gitea:1.20.5-rootless
environment:
- USER_UID=1000
- USER_GID=1000
- GITEA__database__DB_TYPE=postgres
- GITEA__database__HOST=db:5432
- GITEA__database__NAME=db_name
- GITEA__database__USER=db_username
- GITEA__database__PASSWD=db_password
restart: always
volumes:
- ./data:/var/lib/gitea
- ./config:/etc/gitea
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "3000:3000"
- "2222:2222"
depends_on:
- db


Разберём, что и зачем:
У нас будет два сервиса: db и git.

Первый сервис db - это контейнер с СУБД PostgreSQL.
Второй сервис git - это контейнер с Gitea.

Прописываемые параметры:
- image: - указывает на то, какой образ из Docker Hub будет использоваться.
- restart: - указывает на правила перезагрузки сервера в случае сбоя.
- environment: - указывает на параметры переменной среды. В нашем случае это: имя пользователя, пароль, имя базы данных, указание на расположение файлов.
- ports: - указывает какие порты открыты в контейнере, <внешний_порт>:<внутренний_порт>.
- volumes: - указывает на подключённые внутри контейнера директории: <директория_вне_контейнера>:<директория_внутри_контейнера>.
- depends_on - определяет порядок запуска контейнеров, т.е. контейнер с Gitea запустится после запуска контейнера с БД.

Обратите внимание на порты в сервисе git.
Порт 3000 - это порт веб-интерфейса, его можно изменить на удобный вам.
Порт 2222 - это порт SSH-подключения, его также можно изменить более на удобный.

Запуск и первая настройка.
Для запуска используем команду docker compose up -d.
После того, как запустятся контейнеры, открываем браузер и переходим по адресу: https://<ip_сервера>:3000/ или https://<ваш_домен>:3000/, если у вас к серверу прикреплено доменное имя.

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

Настройки базы данных.
В этом блоке указываем данные для подключения к БД, но если вы прописали их в docker-compose-файле, пропускаем этот блок.
Иначе заполняем данными, выбирая удобную для вашего решения СУБД.

Основные настройки.
В этом блоке указываем:
- Название сайта.
- Настройки путей внутри контейнера.
- Домен или ip-адрес сервера.