Код на салфетке
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
`dispatch()` работает следующим образом:
1. Когда клиентский запрос поступает на представление, Django вызывает метод `dispatch()` этого представления.
2. `dispatch()` анализирует метод HTTP запроса (GET, POST, PUT, DELETE и т. д.), который пришел от клиента.
3. В зависимости от метода запроса, `dispatch() вызывает соответствующий метод класса представления. Например, если это GET-запрос, `dispatch()` вызовет метод `get(), если это POST-запрос - метод `post()`, и так далее.
4. После обработки запроса соответствующим методом, `dispatch()` возвращает HTTP-ответ.

В нашем методе, мы проверяем, является ли пользователь переданный в адресной строке текущим авторизованным пользователем.
Если является - вызываем метод dispatch() из родительского класса, в противном случае возвращаем 403 Forbidden.

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

В качестве аргумента для формы UserInfoForm мы передаём ключевой аргумент instance с объектом пользователя.
А для формы UserPasswordForm, мы передаём объект пользователя в качестве позиционного аргумента.

Третьим мы переопределим метод post. Он достаточно большой и с первого взгляда можно запутаться, но всё достаточно тривиально.

В теле метода у нас находится if-elif-else конструкция.
- if проверяет отправку формы с изменением данных.
- elif проверяет отправку формы изменения пароля.
- else возвращает эту же страницу без изменений.

Структура внутри if и elif в целом одинаковая, различается только действие в случае успеха.

В переменную form помещаем нашу форму, передав в неё объект пользователя и данные отправленные с формы на сайте.
Напомню, что для UserInfoForm, объект пользователя передаётся как ключевой аргумент instance, т.е. он будет вторым аргументом, а данные формы как позиционный, т.е. первым.
В случае с формой UserPasswordForm, оба аргумента передаются как позиционные и первым идёт объект пользователя, а вторым данные формы.

Затем проверяем валидность формы.

Если форма валидна и данные корректны:
Сохраняем форму, тем самым внеся изменения в связанный с ней объект пользователя.
Записываем сообщение об успешном изменении, используя функцию messages.
Возвращаем результат.

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

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

Если форма не валидна:
Получаем контекст представления и при помощи метода render обновляем страницу, включающую в себя введённые ранее пользователем данные.
Код представления:
class UserSettingsView(LoginRequiredMixin, TemplateView):  
template_name = 'user_app/profile_settings_page.html'

def dispatch(self, request, *args, **kwargs):
if request.user == get_object_or_404(User, username=self.kwargs.get('username')):
return super().dispatch(request, *args, **kwargs)
else:
raise HttpResponseForbidden("Вы не имеете доступа к этой странице.")

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_info_form'] = forms.UserInfoForm(instance=self.request.user)
context['user_password_form'] = forms.UserPasswordForm(self.request.user)
context['title'] = f'Настройки профиля {self.request.user}'
return context

def post(self, request, *args, **kwargs):
if 'user_info_form' in request.POST:
form = forms.UserInfoForm(request.POST, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, 'Данные успешно изменены.')
return redirect('user_app:user_profile_settings', form.cleaned_data.get('username'))
else:
context = self.get_context_data(**kwargs)
context['user_info_form'] = form
return render(request, self.template_name, context)
elif 'user_password_form' in request.POST:
form = forms.UserPasswordForm(request.user, request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Пароль успешно изменён.')
return self.get(request, *args, **kwargs)
else:
context = self.get_context_data(**kwargs)
context['user_password_form'] = form
return render(request, self.template_name, context)
else:
return self.get(request, *args, **kwargs)



**Шаблон страницы настроек профиля.**
В директории с шаблонами приложения user_app создадим файл profile_settings_page.html.

В этом файле прописываем две формы: отображение сообщения об успешном изменении и кнопку возврата в профиль.

Обратите внимание на код кнопок отправки формы. В них мы указываем атрибут name. Именно по этому атрибуту мы отслеживаем в представлении то, какая форма была отправлена.
Код шаблона:
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}

{% block content %}
<div class="container mt-3">
<div class="row d-flex justify-content-between align-items-center">
<div class="col-lg-10 col-sm-12">
<h2>Настройки профиля {{ user }}</h2>
</div>
<div class="col-lg-2 col-sm-12 text-right">
<a class="btn btn-primary my-btn h-100"
href="{% url 'user_app:user_profile' username=user.username %}">Вернуться в профиль</a>
</div>
</div>
<hr>
<div class="row d-flex justify-content-evenly">
<div class="row text-center">
{% if messages %}
<p class="alert alert-success">
{% for message in messages %}
{{ message }}<br>
{% endfor %}
</p>
{% endif %}
</div>
<div class="col-lg-4 col-sm-12">
<h3 class="mb-3 mt-3">Изменить данные:</h3>
<form method="post">
{% csrf_token %}
{{ user_info_form.as_p }}
<button class="btn btn-primary my-btn mt-2" type="submit" name="user_info_form">Отправить</button>
</form>
</div>
<div class="col-lg-4 col-sm-12">
<h3 class="mb-3 mt-3">Изменить пароль:</h3>
<form method="post">
{% csrf_token %}
{{ user_password_form.as_p }}
<button class="btn btn-primary my-btn mt-2" type="submit" name="user_password_form">Отправить
</button>
</form>
</div>
</div>
</div>
{% endblock %}



Страница с постами пользователя.

Представление.

В файле views.py создадим класс UserPostsView, унаследованный от ListView.

Пропишем три поля:
- template_name - файл шаблона.
- context_object_name - имя переменной в шаблоне
- paginate_by - количество элементов на странице

Переопределим метод get_queryset. В нём вернём список постов, отфильтрованных по переданному в URL-адресе пользователю.

В переопределённом методе get_context_data, в блоке try-except получим объект пользователя по имени пользователя из URL-адреса и запишем его в контекст.

Код представления:
class UserPostsView(ListView):  
template_name = 'user_app/user_posts_page.html'
context_object_name = 'posts'
paginate_by = 10

def get_queryset(self):
return PostModel.post_manager.filter(author__username=self.kwargs.get('username'))

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
try:
author = get_object_or_404(User, username=self.kwargs.get("username"))
except User.DoesNotExist:
raise Http404("Пользователь не найден")
context['author'] = author
context['title'] = f'Посты пользователя {author}'
return context


**Шаблон.**
В директории с шаблонами приложения user_app создадим файл user_posts_page.html.

В качестве шаблона я использовал код из файла tag_page.html.


URL-паттерны страницы настроек и всех постов пользователя.
Откроем файл urls.py в директории приложения user_app и добавим следующие строки в список urlpatterns:
path('<str:username>/settings/', views.UserSettingsView.as_view(), name='user_profile_settings'),  
path('<str:username>/posts/', views.UserPostsView.as_view(), name='user_posts'),



Завершение.
Осталось изменить шаблон страницы профиля, описанный в прошлом посте, а именно, исправить код кнопок.

Кнопка все посты:
<a class="btn btn-primary my-btn mb-3"  
href="{% url 'user_app:user_posts' username=user_profile.username %}">Все посты {{ user_profile }}
</a>


Кнопка настроек профиля:
<a class="btn btn-primary my-btn me-1"  
href="{% url 'user_app:user_profile_settings' username=user_profile.username %}">Настройки профиля
</a>
Готово. За эти два поста, мы реализовали публичный профиль пользователя, страницу настроек и страницу всех постов, используя стандартную модель пользователя Django.

Файлы к посту, можно получить в боте по коду: 828730
Пост на сайте.
Поддержать канал.
🔥2👏1
Позавчера было ровно 15 месяцев как началась учёба в GB.

Как же блин быстро летит время.

Очередной пост на Пикабу: Обучение: пятнадцатый месяц

😉Расчехляйте лайкомёты)
🔥3
😂После релиза программисты становятся лучшими партизанами, а тестировщики ищейками)
🔥4💯1
👴👴👴
1
😂Баян, но блин, не без доли правды)
😁1
Не стареющий баянчик вам в ленту)
🔥5😁2
Приветствую.

У меня почему-то Хэллоуин ассоциируется не то с комедией, не то с трэш-хоррорами. И вот в преддверии этого события, у меня для вас забавный фильм.


Фильм на сегодня: Смерть ей к лицу (1992).

Краткий синопсис: Эликсир вечной молодости! Сколько женщин мечтают о нем! О нем мечтали и бродвейская звезда Мэдлин Эштон, и Хелен Шарп, у которой эта коварная Мэдлин отбила жениха Эрнеста Мэнвилла, гениального врача-специалиста по пластическим операциям.

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

Посмотреть можно тут: https://www.sspoisk.ru/film/1889/

А какие ассоциации у вас вызывает Хэллоуин?
👍2
Django 35.1. Расширенный профиль пользователя - модель и сигналы

Продолжаем тему профиля пользователя, начатую в прошлом посте.

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

Я решил расширить профиль следующими полями:
- Пол
- Дата рождения
- Личный сайт - ссылка на сайт пользователя.
- Профиль в Telegram - ссылка на Telegram-профиль.
- Аватарка - изображения по умолчанию у меня не будет, да и скучно это, когда много пользователей с одинаковыми изображениями. Вместо этого, для каждого пользователя по умолчанию будет генерироваться аватар в сервисе https://robohash.org/.
- Три переключателя, определяющих отображать в профиле или нет следующие поля: Фамилия, Email, Telegram-профиль.
В профиле это будет отображаться несколько иначе:
- Вместо даты рождения, будет отображаться возраст пользователя, если указана дата.
- Ссылка на профиль в Telegram, будет отображать только ник.
- Также будет поле для отображения информации о том, как давно зарегистрировался пользователь.


Библиотека Pillow.
Для работы с изображениями необходима библиотека Pillow, если она у вас не установлена, то установить её можно командой:
pip install Pillow

Не забудьте добавить библиотеку в файл requirements.txt:
Pillow~=10.1.0


Модель профиля.
Откроем файл models.py в директории user_app и создадим класс ProfileModel, унаследованный от models.Model.

В самом начале пропишем внутренний класс Genders, это будет перечисление с вариантами выбора пола пользователя. Подобный класс перечисления мы писали в посте "Django 20. Модель поста".

Далее будет девять полей:
- user - поле для связи с пользователем через связь "Один к Одному" (OneToOne). В аргументах передаём модель, название, а так же имя связи, по которому мы сможем обратиться к объекту модели из объекта пользователя.
- gender - текстовое поле для хранения выбранного в перечислении значения пола пользователя. В аргументах указываем максимальную длину поля, указываем класс с перечислениями, указываем стандартное значение, а также имя поля.
- dob - поле с датой рождения пользователя. В аргументах указываем null и blank равное True, что делает поле необязательным к вводу, а также имя поля.
- site_link, telegram_link - поля ссылок (URLField) . Стандартные аргументы: имя поля, null и blank, максимальная длина. Для telegram_link добавляем атрибут unuque=True, что бы ссылки на профили не повторялись.
- user_avatar - поле изображения (ImageField). В аргументах передаём путь в директории media, куда будут загружаться изображения, также имя поля и null и blank. Также, можно указать изображение по умолчанию.
- show_email, show_telegram, show_last_name - логические поля (BooleanField). В аргументах передаём имя поля и стандартное значение False.

Прописываем класс Meta, указывая имя модели.

Затем у нас будет несколько методов:

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

Метод get_on_site, высчитывающий из текущей даты и даты регистрации то, сколько дней/месяцев/лет назад зарегистрировался пользователь.

Метод get_telegram_username, отделяющий от ссылки на профиль имя пользователя в Telegram.

Метод get_age, высчитывающий возраст пользователя.

Dunder-метод __str__, возвращающий имя пользователя при обращении к объекту.

Код модели:
Код доступен на сайте или в файлах к посту


Регистрация в панели администратора.
Откроем файл admin.py в директории user_app.

Я не вижу смысла тут прописывать поля для отображения, по крайней мере пока, по этому просто зарегистрирую модель:
from django.contrib import admin  

from user_app import models

admin.site.register(models.ProfileModel)


Сигнал о регистрации пользователя.
Когда пользователь регистрируется на сайте, необходимо, что бы создавалась модель профиля, с этим нам помогут сигналы (signals) в Django.

Сигналы (signals) - это механизм, который позволяет отправлять сообщения о событиях в приложении. Сигналы используются для оповещения других частей приложения о том, что произошло определенное событие, например, создание или обновление объекта модели.

В директории приложения user_app создадим новый файл signals.py и напишем следующий код:
from django.contrib.auth.models import User  
from .models import ProfileModel
from django.db.models.signals import post_save
from django.dispatch import receiver


@receiver(post_save, sender=User)
def create_profile(sender, instance, created, **kwargs):
if created:
ProfileModel.objects.create(user=instance)
create_profile - это функция-обработчик сигнала `post_save`, который вызывается каждый раз, когда сохраняется экземпляр модели `User`.
В нашем случае он будет отрабатывать только при создании нового объекта, т.е. регистрации пользователя. Для этого в теле функции есть условие if created.
Если условие верное, то будет создан экземпляр модели `ProfileModel` и связан с экземпляром модели `User`, который был передан в функцию обработчиком сигнала.


**Подключение сигнала.**
Откроем файл apps.py и внутри класса добавим метод ready, который будет срабатывать при запуске приложения:
def ready(self):  
import user_app.signals


Внимание! Если у вас уже есть зарегистрированные пользователи, а как минимум один (суперпользователь) у вас есть, вам необходимо для них в админке создать профили самостоятельно!

Файлы к посту, можно получить в боте по коду: 585887
Пост на сайте.
Поддержать канал.
🔥1
Django 35.2. Расширенный профиль пользователя - форма и шаблон

Мы написали модель и сделали сигнал о регистрации.
Теперь займёмся расширенной формой и доработкой шаблона.


Форма модели профиля.
В форме пропишем следующие поля: gender, dob, site_link, telegram_link, user_avatar, show_last_name, show_email, show_telegram.

В целом класс формы не отличается от других ранее созданных нами форм.
Описываем поле, указываем его тип, прописываем название поля и прописываем CSS-класс, также прописываем, что все поля необязательны к вводу.
Опишу только новые или отличающиеся поля:
- gender - поле выбора ChoiceField. В аргументах указываем название поля, доступные выборы параметром choices и виджет Select.
- dob - поле даты DateField. Указываем название поля и виджет DateInput. В аргументах к виджету передаём параметр format. С этим связана забавная особенность. В БД дата хранится в виде ГГГГ-ММ-ДД, однако в нашей локали принята запись ДД.ММ.ГГГГ. В шаблон оно передаётся в нашем виде, но HTML-форма даты принимает только в формате ГГГГ-ММ-ДД, при этом отображает в формате пользовательской локали. Сложно и не понятно. Для решения этой ситуации указываем параметр format='%Y-%m-%d'. Также в параметр attrs, помимо класса стиля, передаём тип формы 'type': 'date', т.к. по умолчанию DateInput это просто текстовое поле.
- user_avatar - поле загрузки файлов FileField. В атрибутах название поля и виджет FileInput. В параметре attrs виджета, кроме класса, указываем тип поля 'type': 'file'.
- show_last_name, show_email, show_telegram - логические поля BooleanField. Виджет поля CheckboxInput.

Ниже класс Meta с указанием модели ProfileModel и перечислением полей.

Код формы:
Много текста. Код доступен на сайте или в материалах к посту.



Изменение представления страницы настроек профиля.
Для добавления новой формы нам надо добавить несколько строк.

В метод get_context_data добавляем строку с передачей новой формы в шаблон, передавая в качестве аргумента объект профиля:
context['user_profile_form'] = forms.ProfileForm(instance=self.request.user.profile)


В методе post, в первом условии if 'user_info_form' in request.POST:, добавляем новую переменную формы, передавая в неё данные из POST-запроса, файл из POST-запроса и объект профиля.
user_profile_form = forms.ProfileForm(request.POST, request.FILES, instance=self.request.user.profile)


Далее в проверку на валидность, добавляем проверку и второй формы:
if user_info_form.is_valid() and user_profile_form.is_valid():


После чего, если обе формы валидны, сохраняем объекты с новыми данными:
user_info_form.save()  
user_profile_form.save()


Если одна или обе формы не валидны, возвращаем формы с введёнными данными:
context['user_info_form'] = user_info_form  
context['user_profile_form'] = user_profile_form


Код изменённых методов:
Много текста. Код доступен на сайте или в материалах к посту.



Шаблон страницы настроек:

Тут мы объединяем обе формы в одном теге form. Дополнительным аргументом указываем enctype="multipart/form-data", сообщая, что эта форма может принимать файлы.

Далее, можно по стандарту вывести обе формы:
{% csrf_token %}
{{ user_info_form.as_p }}
{{ user_profile_form.as_p }}


Но я для лучшего отображения применил классы Bootstrap. Для этого пришлось каждое поле выводить по отдельности.

Код шаблона:
Много текста. Код доступен на сайте или в материалах к посту.



Шаблон страницы профиля.
Тут почти стандартно, отображаем нужные поля профиля с проверкой на наличие данных в них:
Много текста. Код доступен на сайте или в материалах к посту.


На этом всё. У нас теперь есть расширенный профиль пользователя с необходимыми полями. Вероятно, в будущем он будет изменяться или расширяться. Следующим шагом будет создание своего расширенного пользователя, но этим мы займёмся в одном из следующих постов.

Файлы к посту, можно получить в боте по коду: 206476
Пост на сайте.
Поддержать канал.
🔥1
Ох уж эти тестеры, любого программиста запугают))
😁4
Приветствую.

Хочу с вами поделиться своей радостью!

Я не так давно писал, что подал заявку на стажировку в IT-академию Lad.
Для прохождения необходимо было выполнить практическое задание и пройти тест на знания языка Python и Фреймворка Django.
По результатам отбора среди более 2000 заявок, после всех отборочных испытаний, я оказался в сотне счастливчиков, допущенных к стажировке.

С сегодняшнего дня начинается моя стажировка в IT-академии Lad, длительностью пять месяцев.

Этим постом начинаю новую рубрику "Вести с полей стажировки".
Буду рассказывать о процессе стажировки, нюансах и новых знаниях.
🔥15
Несколько месяцев тому назад, поступала идея провести "Тайного Санту" среди подписчиков канала.

С того времени идея как-то витала в воздухе, но руки не доходили её воплотить, пока не поступил резонный вопрос - "Где?".

Ну и вот, собравшись с мыслями, замучив Секретаря, встречайте - Тайный Дед Котямба!

Для участия, необходимо заполнить форму: https://forms.gle/adM65uCYJEPgSggW7

И желательно вступить в чат канала: https://t.iss.one/pressanybutton_chat

Давайте подарим друг-другу в этот новый год капельку загадочности и счастья 🙂
🎄3🥰1
Небольшая поправка к посту "Django 35.1. Расширенный профиль пользователя - модель и сигналы".

Я не учёл того, что пользователь может ввести имя на кириллице(или любом другом, отсутствующем в таблице ASCII языке).

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

Код в посте поправил, на сайте пофиксил 😎
🔥2
Статистика сайта за месяц
Автор: proDream

Прошёл месяц с прошлого поста со статистикой сайта, что изменилось?

Переходы с Dzen'а упали, там в целом показы и просмотры просели, хотя и до этого не то, что бы были большие.
Переходы с канала упали, обидно 🙁.
И тут выходит поиск. За месяц показы и, что не мало важно, клики, сильно скаканули. Это приятно. Чаще всего ищут статьи про AIOgram(это при том-то, что на сайте сплошная джанга).

Также добавил на сайт небольшой блок с рекламой. Никаких всплывашек или баннеров на весь экран. Небольшой ненавязчивый блок. По этому большая просьба внести сайт в исключения блокировщика рекламы. Вам не сложно, а мне может хоть на оплату сервера капать будет 🙂.
👍2
В сухом итоге:
- Посетители: Было 210, стало 500. Прирост 138.09%.
- Переходы из Dzen: Было 37.6%, стало 12.6%. Падение 66.48%.
- Переходы из Telegram-канала: Было 24.2%, стало 18%. Падение 25.61%.
- Переходы из поиска: Было 17.6%, стало 48.1%. Прирост 173.29%.

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