Код на салфетке
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?
Anonymous Quiz
12%
django-admin appstart myproject
16%
python manage.py newapp myproject
60%
python manage.py startapp myproject
4%
django-admin projectapp myproject
8%
python manage.py app myproject
🔥1
Django 27.1 Представления на основе классов

В предыдущих постах я рассказывал о представлениях на основе функций, они же function-based views или FBV.

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


Классовые представления.
Классовые представления, они же class-based views или CBV. Представляют собой альтернативный подход к созданию представлений. Они основаны на использовании классов вместо функций, что позволяет лучше структурировать код и повысить его переиспользование.

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

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

Различия между функциональными и классовыми представлениями:
1. Структура кода: В функциональных представлениях код разбит на функции, которые выполняют определенные действия. В классовых представлениях код организован в виде классов, которые содержат методы для обработки запросов.
2. Наследование: В классовых представлениях можно использовать наследование, что позволяет переиспользовать код и добавлять новую функциональность. В функциональных представлениях такая возможность отсутствует.
3. Расширяемость: Классовые представления позволяют легко добавлять новые методы и функциональность без изменения основного кода. В функциональных представлениях это может быть сложнее.
4. Миксины: Классовые представления поддерживают использование миксинов, которые представляют собой классы с определенными методами, которые можно добавить к основному классу представления для расширения его функциональности.
5. Читаемость кода: Классовые представления могут быть более читаемыми и понятными, особенно для разработчиков, знакомых с объектно-ориентированным программированием.


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

Вот некоторые из них:
1. View: Это самый базовый класс представления в Django. Он обрабатывает связывание представления с URL, диспетчеризацию методов HTTP и другие общие функции. Все другие классы представлений Django наследуются от этого класса.
2. TemplateView: Этот класс представления используется, когда вам нужно просто отобразить страницу, которая не требует никакой дополнительной логики. Он автоматически рендерит указанный шаблон.
3. ListView: Это класс представления, который используется для отображения списка объектов. Он автоматически предоставляет контекст для шаблона, который содержит список объектов.
4. DetailView: Этот класс представления используется для отображения деталей конкретного объекта. Он автоматически предоставляет контекст для шаблона, который содержит объект.
5. CreateView, UpdateView, DeleteView: Эти классы представлений используются для создания, обновления и удаления объектов соответственно. Они автоматически предоставляют форму для ввода данных и обрабатывают POST-запросы для создания, обновления или удаления объектов.
6. FormView: Этот класс представления используется для отображения и обработки форм. Он автоматически предоставляет форму для ввода данных и обрабатывает POST-запросы.
7. RedirectView: Этот класс представления используется для перенаправления на другой URL.
8. DateDetailView, YearArchiveView, MonthArchiveView, DayArchiveView, TodayArchiveView: Эти классы представлений предназначены для отображения архивов объектов, отсортированных по датам.

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


Теории много, но без неё было никак. В следующей части начнём переделывать представления из функциональных в классовые.
🔥3
Django 27.2 Представления на основе классов - Практика

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

На данный момент у меня в проекте 4 представления, а именно:
- Главная страница
- Страница категории
- Страница поста
- Страница тега

Откроем файлы views.py и urls.py.
Представление главной страницы.
Вот так представление выглядит на данный момент:
def index(request):  
categories_list = models.CategoryModel.objects.all()
latest_posts = models.PostModel.post_manager.latest_posts()

context = {
'categories_list': categories_list,
'latest_posts': latest_posts,
}

return render(request,
'blog/index.html',
context)

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

Для того, чтобы переписать это в классовый вид, создадим новый класс IndexView и унаследуем его от TemplateView.

В классе пропишем единственное поле - template_name и присвоим ему строку с путём до шаблона главной страницы.

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

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

В конце делаем возврат контекста.

Код:
from django.views.generic import TemplateView


class IndexView(TemplateView):
template_name = 'blog/index.html'

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories_list'] = models.CategoryModel.objects.all()
context['latest_posts'] = models.PostModel.post_manager.latest_posts()
return context

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


URL-паттерн главной страницы.
У нас уже есть паттерн для главной страницы:
path('', views.index, name='index'),

Он ведёт на функцию, для класса, его необходимо слегка изменить, а именно поменять имя функции index на имя класса IndexView и добавить обращение к методу as_view(), сообщающему, что мы обращаемся к классу-представлению.
path('', views.IndexView.as_view(), name='index'),


Представление страницы категории.
Сейчас представление выглядит так:
def category_page(request, category_slug):  
category = models.CategoryModel.objects.get(slug=category_slug)
descendant_categories = category.get_descendants(include_self=True)
posts = models.PostModel.objects.filter(category__in=descendant_categories)
categories_list = models.CategoryModel.get_children(self=category)

context = {
'category': category,
'posts': posts,
'categories_list': categories_list,
}

return render(request,
'blog/category_page.html',
context)

Для вывода списка постов на странице категории будем использовать ListView.
Создадим класс CategoryPageView, который наследует `ListView`.

В данном классе будет 3 поля, 2 переопределённых метода и один собственный метод.


Поля класса.
Первое поле model, в нём мы передаём ссылку на модель, с которой будет представление. В нашем случае это модель поста.
Второе поле, как и в предыдущем классе - template_name.
В третьем поле context_object_name, мы определяем то, как будет называться переменная к которой мы обращаемся в шаблоне, у нас это - posts.


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

Логика проста, мы не задали поле класса для хранения объекта категории. При обращении к методу, проиводится проверка, существует или нет поле с именем category.
Если поле существует, возвращается его значение.
Если поле не существует, то оно создаётся и в него мы присваиемаем полученные из БД данные.
Обратите внимание, PyCharm и возможно другие IDE будут сигнализировать, что в классе не задано поле category, игнорируем сообщение, поскольку именно в этом и смысл.


Переопределённые методы.
В предыдущем классе, мы переопределили только один метод get_context_data, переопределим его и в этом классе.

В методе добавим в контекст объект текущей категории и список подкатегорий.

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

Код.
from django.views.generic import ListView


class CategoryPageView(ListView):
model = models.PostModel
template_name = 'blog/category_page.html'
context_object_name = 'posts'

def get_queryset(self):
category = self.get_category()
descendant_categories = category.get_descendants(include_self=True)
return models.PostModel.objects.filter(category__in=descendant_categories)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
category = self.get_category()
context['category'] = category
context['categories_list'] = models.CategoryModel.get_children(self=category)
return context

def get_category(self):
if not hasattr(self, 'category'):
self.category = models.CategoryModel.objects.get(slug=self.kwargs['category_slug'])
return self.category


По аналогии с предыдущим классом, измените URL-паттерн.
path('category/<slug:category_slug>/', views.CategoryPageView.as_view(), name='category_page'),


Представление страницы поста.
Сейчас представление выглядит так:
def post_page(request, category_slug, slug):  
post = get_object_or_404(models.PostModel, slug=slug)
post.views += 1
post.save()

context = {
'post': post,
}

return render(request,
'blog/post_page.html',
context)

Для классового представления страницы поста будем использовать DetailView.

Создадим класс PostPageView, наследующий DetailView и пропишем такие же как в классе страницы категории, поля, а именно - model, template_name и context_object_name.

Переопределим метод get_object.
В этом методе мы будем получать объект текущего поста и увеличивать счётчик просмотров на 1.
Метод будет вызываться автоматически при открытии поста.

Код.
from django.views.generic import DetailView


class PostPageView(DetailView):
model = models.PostModel
template_name = 'blog/post_page.html'
context_object_name = 'post'

def get_object(self, queryset=None):
obj = super().get_object(queryset=queryset)
obj.views += 1
obj.save()
return obj

И изменим URL-паттерн:
path('post/<slug:category_slug>/<slug:slug>/', views.PostPageView.as_view(), name='post_page'),


Представление страницы тега.
Последнее, четвёртое представление. Сейчас оно такое:
def tag_page(request, tag_name):  
posts = models.PostModel.objects.filter(tags__slug=tag_name).distinct()

context = {
'tag_name': tag_name,
'posts': posts,
}

return render(request,
'blog/tag_page.html',
context)

Последний, самый простой, в том плане, что он такой же как и представление категории.

Создаём класс TagPageView, унаследованный от ListView.
В классе прописываем три поля, как в предыдущие два раза.

Переопределяем метод get_queryset, возвращающий посты относящиеся к текущему тегу.

Переопределяем метод get_context_data, добавляя в контекст объект текущего тега.
Код.
class TagPageView(ListView):  
model = models.PostModel
template_name = 'blog/tag_page.html'
context_object_name = 'posts'

def get_queryset(self):
tag_name = self.kwargs['tag_name']
return models.PostModel.objects.filter(tags__slug=tag_name).distinct()

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tag_name'] = self.kwargs['tag_name']
return context


И конечно же, URL-паттерн:
re_path(r'^tag/(?P<tag_name>[\w-]+)/$', views.TagPageView.as_view(), name='tag_page'),



Теперь, если запустить Django, то всё будет как прежде.

Файлы к посту, можно получить в боте по коду: 546974
🔥1
Состоялся релиз третьей версии AIOgram.

Обновить версию можно с помощью PyPI: pip install -U aiogram.
Но имейте в виду, что в этой версии есть много критических изменений относительно второй версии.

В связи с этим, я подготовил перевод документации по миграции со второй на третью версию библиотеки.
Ознакомиться с материалом можно на сайте.
🔥3
Было две викторины, на тему создания Django проекта и Django приложения.
Результаты следующие:
- Создание Django проекта - 46% верных ответов среди 28 участвующих.
- Создание Django приложения - 58% верных ответов среди 24 участвующих.

В связи с этим небольшой пост, по созданию проекта и приложения с кратким описанием структуры.
🔥1
Создание Django проекта
Чтобы создать проект в Django, нам нужно открыть терминал и перейти в папку, где мы хотим создать наш Django проект.

Здесь мы запускаем команду:

django-admin startproject myproject


Эта команда создаст для нас новый каталог, названный myproject, и заполнит его файлами, необходимыми для создания проекта Django.


Структура проекта Django
Давайте рассмотрим структуру директорий и файлов, созданных командой startproject.

myproject/
|-- myproject/
| |-- __init__.py
| |-- settings.py
| |-- urls.py
| |-- asgi.py
| `-- wsgi.py
`-- manage.py


- myproject/ - это каталог верхнего уровня, который содержит наш Django проект.
- myproject/myproject/ - это каталог, который содержит первое Django приложение.
- myproject/__init__.py - пустой файл, который просто говорит Python, что каталог myproject должен рассматриваться как Python-пакет.
- myproject/settings.py - - файл, содержащий настройки Django-проекта, такие как база данных, интернационализация, часовой пояс, а также другие настройки.
- myproject/urls.py - - файл, содержащий шаблоны URL-адресов проекта Django. Как только вы определите эти шаблоны, Django будет использовать их для пересылки пользователей на соответствующее представление.
- myproject/asgi.py и myproject/wsgi.py - файлы, содержащие настройки для ASGI и WSGI серверов. ASGI - это новый способ работы с асинхронным Python, в то время как WSGI - это протокол для связи Python-приложений и веб-сервера.
- manage.py - исполняемый файл, который используется для запуска различных команд в веб-приложении Django, например запуска сервера разработки, создания миграций базы данных и многое другое.


Создание приложения в Django
Для создания нового приложения в Django необходимо запустить следующую команду из терминала:

python manage.py startapp my_app


Где my_app - это название нового приложения.

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


Структура приложения Django
Рассмотрим структуру каталогов и файлов, которые были созданы при выполнении команды startapp:

my_app/
|-- __init__.py
|-- admin.py
|-- apps.py
|-- models.py
|-- tests.py
`-- views.py


- __init__.py - пустой файл, который указывает Python, что каталог my_app должен рассматриваться как Python-пакет.
- admin.py - файл, содержащий настройки администраторской панели (Django Admin) для вашего приложения.
- apps.py - файл, содержащий настройки приложения, такие как его название.
- models.py - файл, содержащий определения моделей для вашего приложения. Модели - это объектно-реляционная отображаемая таблица базы данных.
- tests.py - файл, содержащий модули тестирования для вашего приложения.
- views.py - файл, содержащий определения представлений (views) для вашего приложения. В этих представлениях определено, что происходит, когда пользователь переходит на страницу вашего сайта.
👍6
Django 28. Добавляем пагинацию на сайт

Пагинация - это способ разделения больших объёмов данных на отдельные страницы, например, вывод списка пользователей, товаров или записей блога.

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

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

Разберём оба варианта.
🔥2
Изменение представлений.

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

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

На примере класса страницы тегов:
class TagPageView(ListView):  
model = models.PostModel
template_name = 'blog/tag_page.html'
context_object_name = 'posts'
paginate_by = 10

# методы класса

Пагинация в FBV.
В функциональных представлениях пагинация добавляется немного другим способом.

В функции представления необходимо добавить три переменные.
1. paginator - ей присваиваем объект класса Paginator, передавая в него переменную со списком постов и количество элементов на страницу.
2. page_number - в неё получаем значение текущей страницы, если его нет, то по умолчанию устанавливаем 1 страницу.
3. posts - переменная, в которую получаем список постов для конкретной страницы

На примере функции страницы тегов:
from django.core.paginator import Paginator


def tag_page(request, tag_name):
posts_list = models.PostModel.objects.filter(tags__slug=tag_name).distinct()

paginator = Paginator(posts_list, 10)
page_number = request.GET.get('page', 1)
posts = paginator.page(page_number)

context = {
'tag_name': tag_name,
'posts': posts,
}

return render(request,
'blog/tag_page.html',
context)


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

Для оформления вывода будем использовать стили пагинатора из Bootstrap.

Перейдём в директорию шаблонов и создадим файл pagination.html. Для удобства можно создать директорию modules и поместить его туда.

В файле напишем три блока:
- Первый отвечает за кнопку назад, функционал и отображение.
- Второй за кнопки страниц.
- Третий за кнопку вперёд.

Код.
<nav aria-label="Page navigation">  
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Назад</a></li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Назад</span>
</li> {% endif %}

{% for num_page in page_obj.paginator.page_range %}
{% if num_page == page_obj.number %}
<li class="page-item active"><a class="page-link" href="#">{{ num_page }}</a></li>
{% else %}
<li class="page-item"><a class="page-link" href="?page={{ num_page }}">{{ num_page }}</a></li>
{% endif %}
{% endfor %}

{% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Вперед</a></li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Вперед</span>
</li> {% endif %}
</ul>
</nav>

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


Подключаем на странице.
Теперь этот файл необходимо подключить в шаблоне страницы, продолжим пример на странице тегов.
Откроем файл tag_page.html.

До закрытия тега div с классом container добавляем следующий блок:

Для CBV:
<div class="col-12">  
{% include "blog/modules/pagination.html" %}
</div>

Для FBV:
<div class="col-12">  
{% include "blog/modules/pagination.html" with page_obj=posts %}
</div>

Обратите внимание. Во втором случае, мы передаём в шаблон переменную posts под видом переменной page_obj. Этого можно и не делать, заменив в файле pagination.html все page_obj на posts

Теперь можно запустить Django и убедиться, что пагинация работает.

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

Сегодня, в день программиста.

Хочу пожелать всем причастным хорошего и чистого кода, не требовательных заказчиков и что бы все задачи решаемые вами были в радость, а не вызывали отторжение.
12
Django 29.1 Добавляем поиск на сайт

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

В Django есть простой механизм поиска с помощью фильтра contains или icontains. Первый чувствителен к регистру, а второй нет.
Например: PostModel.objects.filter(full_body__icontains='поисковый запрос').
Но такой поиск ищет "в лоб" точно подходящие слова или фразы.

Помимо этого способа, используя СУБД PostgreSQL, открываются расширенные возможности поиска. Такие, как выдача результатов в зависимости от частоты встречаемого запроса в тексте или поиск по триграммному сходству.

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

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

Код класса:
from django.shortcuts import render  
from django.views.generic import TemplateView
from django.core.paginator import Paginator

from blog import models, forms

class SearchPageView(TemplateView):
template_name = 'blog/search.html'

def get(self, request, *args, **kwargs):
form = forms.SearchForm(request.GET)
if form.is_valid():
query = form.cleaned_data['query']

# ... тут код поиска

paginator = Paginator(results, 10)
page_number = request.GET.get('page', 1)
results = paginator.get_page(page_number)

context = {"query": query,
"results": results}

return render(request,
self.template_name,
context)

return render(request,
self.template_name,
{"query": request.GET['query']})


В теле класса пропишем поле template_name в котором укажем на шаблон страницы поиска.

Далее создадим метод get, принимающий self, request, *args, **kwargs.

В нём создадим переменную form и присвоим ей нашу будущую форму поиска, передав в неё GET-запрос - forms.SearchForm(request.GET)

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

Далее идёт код поиска, его рассмотрим чуть позже.

Затем, поскольку результатов может быть много, мы разбиваем их на страницы используя пагинацию.

Создаём переменную context, передавая в неё поисковый запрос и результаты поиска.

Ниже возвращаем рендер страницы с результатами поиска. А вне блока if прописываем ещё один рендер, возвращающий страницу с поисковым запросом, но без результатов.

Теперь рассмотрим два варианта реализации поиска.

**Поиск по частоте упоминания в тексте или полнотекстовый поиск**
Код:
from django.contrib.postgres.search import SearchVector, SearchRank, SearchQuery

search_vector = SearchVector('title', 'full_body')
search_query = SearchQuery(query)
results = (models.PostModel.post_manager.annotate(search=search_vector,
rank=SearchRank(search_vector, search_query,
cover_density=True)
).filter(search=search_query)).order_by('-rank')



Сначала определяем поля для поиска с помощью `SearchVector`. В нашем случае, поиск будет производиться по полям 'title' и 'full_body'.

Затем создаем объект `SearchQuery`, который представляет собой запрос для поиска, передавая в него поисковый запрос.

Затем осуществляется фильтрация и сортировка результатов поиска:

Обращаясь к нашему собственному менеджеру постов, выдающему только опубликованные посты, методом annotate` к списку постов мы добавляем новое поле `search. Которое содержит поисковые вектора.

SearchRank` используется для присвоения ранга каждому результату поиска, с учетом `search_vector` и `search_query. Здесь установлен параметр `cover_density=True`, чтобы учесть плотность ключевых слов на странице.

В итоге, результаты фильтруются по `search_query` и сортируются в порядке убывания ранга (-rank).


Поиск по сходству или триграммный поиск.
Код:
from django.contrib.postgres.search import TrigramSimilarity

similarity = (TrigramSimilarity('full_body', query) +
TrigramSimilarity('title', query))
results = models.PostModel.post_manager.annotate(
similarity=similarity
).filter(similarity__gt=0.1).order_by('-similarity')
В переменной similarity находится результат применения `TrigramSimilarity` - Это функция, которая на основе алгоритма трехграмм вычисляет сходство между строками. В нашем случае функция применяется к полям 'full_body' и 'title'.

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

Затем происходит фильтрация результатов. Используется фильтрация по значению `similarity`, где сходство больше 0.1. То есть, в результаты попадут только те посты, у которых сходство с запросом больше 0.1.

В итоге сортируем результаты в порядке схожести с запросом.

Для своего сайта я выбрал второй вариант.

Продолжение в следующем посте.


Пост на сайте.

Поддержать канал.