Django 27.2 Представления на основе классов - Практика
Разобравшись для чего нужны функциональные и классовые представления, перейдём к практике по преобразованию представлений.
На данный момент у меня в проекте 4 представления, а именно:
- Главная страница
- Страница категории
- Страница поста
- Страница тега
Откроем файлы
Разобравшись для чего нужны функциональные и классовые представления, перейдём к практике по преобразованию представлений.
На данный момент у меня в проекте 4 представления, а именно:
- Главная страница
- Страница категории
- Страница поста
- Страница тега
Откроем файлы
views.py и urls.py.Представление главной страницы.
Вот так представление выглядит на данный момент:
Для того, чтобы переписать это в классовый вид, создадим новый класс
В классе пропишем единственное поле -
Также в классе будет всего один метод -
В теле метода, получим из родительского класса экземпляр контекста, представляющий собой словарь.
Дале пропишем две строки, где по ключам добавим данные контекста. Ключом будет переменная для использования в шаблоне, а значением данные.
В конце делаем возврат контекста.
Код:
URL-паттерн главной страницы.
У нас уже есть паттерн для главной страницы:
Сейчас представление выглядит так:
Создадим класс
В данном классе будет 3 поля, 2 переопределённых метода и один собственный метод.
Поля класса.
Первое поле
Второе поле, как и в предыдущем классе -
В третьем поле
Собственный метод.
В нашем классе мы создадим собственный метод
Это сделано для избегания повторных обращений к базе данных, для получения объекта текущей категории в переопределённых методах.
Логика проста, мы не задали поле класса для хранения объекта категории. При обращении к методу, проиводится проверка, существует или нет поле с именем
Если поле существует, возвращается его значение.
Если поле не существует, то оно создаётся и в него мы присваиемаем полученные из БД данные.
Вот так представление выглядит на данный момент:
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, игнорируем сообщение, поскольку именно в этом и смысл.
Переопределённые методы.
В предыдущем классе, мы переопределили только один метод
В методе добавим в контекст объект текущей категории и список подкатегорий.
Вторым переопределённым методом, будет метод
Код.
Сейчас представление выглядит так:
Создадим класс
Переопределим метод
В этом методе мы будем получать объект текущего поста и увеличивать счётчик просмотров на 1.
Метод будет вызываться автоматически при открытии поста.
Код.
Последнее, четвёртое представление. Сейчас оно такое:
Создаём класс
В классе прописываем три поля, как в предыдущие два раза.
Переопределяем метод
Переопределяем метод
Переопределённые методы.
В предыдущем классе, мы переопределили только один метод
get_context_data, переопределим его и в этом классе.В методе добавим в контекст объект текущей категории и список подкатегорий.
Вторым переопределённым методом, будет метод
get_queryset, необходимый для создания списка объектов, в нашем случае постов относящихся к этой и вложенным категориям. Об этом я подробно рассказывал в посте про создание представления страницы категории.Код.
from django.views.generic import ListViewПо аналогии с предыдущим классом, измените URL-паттерн.
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
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И изменим URL-паттерн:
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
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, добавляя в контекст объект текущего тега.Код.
И конечно же, URL-паттерн:
Теперь, если запустить Django, то всё будет как прежде.
Файлы к посту, можно получить в боте по коду: 546974
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:
Но имейте в виду, что в этой версии есть много критических изменений относительно второй версии.
В связи с этим, я подготовил перевод документации по миграции со второй на третью версию библиотеки.
Ознакомиться с материалом можно на сайте.
Обновить версию можно с помощью PyPI:
pip install -U aiogram.Но имейте в виду, что в этой версии есть много критических изменений относительно второй версии.
В связи с этим, я подготовил перевод документации по миграции со второй на третью версию библиотеки.
Ознакомиться с материалом можно на сайте.
🔥3
Было две викторины, на тему создания Django проекта и Django приложения.
Результаты следующие:
- Создание Django проекта - 46% верных ответов среди 28 участвующих.
- Создание Django приложения - 58% верных ответов среди 24 участвующих.
В связи с этим небольшой пост, по созданию проекта и приложения с кратким описанием структуры.
Результаты следующие:
- Создание Django проекта - 46% верных ответов среди 28 участвующих.
- Создание Django приложения - 58% верных ответов среди 24 участвующих.
В связи с этим небольшой пост, по созданию проекта и приложения с кратким описанием структуры.
🔥1
Создание Django проекта
Чтобы создать проект в Django, нам нужно открыть терминал и перейти в папку, где мы хотим создать наш Django проект.
Здесь мы запускаем команду:
Эта команда создаст для нас новый каталог, названный
Структура проекта Django
Давайте рассмотрим структуру директорий и файлов, созданных командой
-
-
-
-
-
-
-
Создание приложения в Django
Для создания нового приложения в Django необходимо запустить следующую команду из терминала:
Где
После выполнения этой команды Django создаст новый каталог с именем
Структура приложения 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
Как определяются магические методы в Python?
Anonymous Quiz
0%
Любой пользовательский метод - магический
94%
Методы, начинающиеся с двух подчёркиваний и заканчивающизся двумя подчёркиваниями
2%
Это синоним статических методов, поэтому через @staticmethod
4%
Магические методы можно определять только на уровне Си
Вопрос с подвохом😉
Как создать телеграм-бота с использованием Aiogram?
Как создать телеграм-бота с использованием Aiogram?
Anonymous Quiz
60%
Установить библиотеку Aiogram и написать код на Python
21%
Использовать готовые шаблоны
19%
Нанять разработчика (например меня 😃)
😁5
Django 28. Добавляем пагинацию на сайт
Пагинация - это способ разделения больших объёмов данных на отдельные страницы, например, вывод списка пользователей, товаров или записей блога.
Пагинация позволяет ускорить загрузку страниц за счёт сегментирования содержимого, а также улучшить взаимодействие пользователя с сайтом.
Добавление функционала достаточно простое, но оно отличается в зависимости от способа написания представлений.
Разберём оба варианта.
Пагинация - это способ разделения больших объёмов данных на отдельные страницы, например, вывод списка пользователей, товаров или записей блога.
Пагинация позволяет ускорить загрузку страниц за счёт сегментирования содержимого, а также улучшить взаимодействие пользователя с сайтом.
Добавление функционала достаточно простое, но оно отличается в зависимости от способа написания представлений.
Разберём оба варианта.
🔥2
Изменение представлений.
Пагинация в CBV.
В прошлом посте я писал о преимуществах классовых представлений от функциональных. И если в моём случае, в коде преимущество не видно сразу, то с пагинацией наоборот.
Для добавления пагинации в данные страницы достаточно добавить в код представления буквально одно поле -
На примере класса страницы тегов:
В функциональных представлениях пагинация добавляется немного другим способом.
В функции представления необходимо добавить три переменные.
1.
2.
3.
На примере функции страницы тегов:
После передачи данных в шаблон их нужно обработать.
Для оформления вывода будем использовать стили пагинатора из Bootstrap.
Перейдём в директорию шаблонов и создадим файл
В файле напишем три блока:
- Первый отвечает за кнопку назад, функционал и отображение.
- Второй за кнопки страниц.
- Третий за кнопку вперёд.
Код.
Подключаем на странице.
Теперь этот файл необходимо подключить в шаблоне страницы, продолжим пример на странице тегов.
Откроем файл
До закрытия тега
Для CBV:
Теперь можно запустить Django и убедиться, что пагинация работает.
Файлы к посту, можно получить в боте по коду: 519494
Пост на сайте
Поддержать канал
Пагинация в CBV.
В прошлом посте я писал о преимуществах классовых представлений от функциональных. И если в моём случае, в коде преимущество не видно сразу, то с пагинацией наоборот.
Для добавления пагинации в данные страницы достаточно добавить в код представления буквально одно поле -
paginate_by с указанием количества элементов на страницу.На примере класса страницы тегов:
class TagPageView(ListView):Пагинация в FBV.
model = models.PostModel
template_name = 'blog/tag_page.html'
context_object_name = 'posts'
paginate_by = 10
# методы класса
В функциональных представлениях пагинация добавляется немного другим способом.
В функции представления необходимо добавить три переменные.
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">Для FBV:
{% include "blog/modules/pagination.html" %}
</div>
<div class="col-12">Обратите внимание. Во втором случае, мы передаём в шаблон переменную posts под видом переменной page_obj. Этого можно и не делать, заменив в файле pagination.html все page_obj на posts
{% include "blog/modules/pagination.html" with page_obj=posts %}
</div>
Теперь можно запустить Django и убедиться, что пагинация работает.
Файлы к посту, можно получить в боте по коду: 519494
Пост на сайте
Поддержать канал
Повторяете ли вы описанное в постах?
Anonymous Poll
6%
Повторяю каждый пост
16%
Периодически повторяю
22%
Сохраняю на будущее
44%
Просто читаю
13%
Не моя область/стек
Что такое ORM и для чего он используется?
Anonymous Quiz
3%
это язык программирования, специально разработанный для создания веб-приложений.
30%
это тип базы данных, который используется для хранения данных в приложении.
49%
это инструмент, который позволяет разработчикам работать с базой данных с использованием ООП.
19%
это шаблон проектирования, который используется для организации кода веб-приложения.
Django 29.1 Добавляем поиск на сайт
щё одной немаловажной частью сайта является поиск. У пользователя должна быть возможность найти материал по запрошенному слову или фразе.
В Django есть простой механизм поиска с помощью фильтра
Например:
Но такой поиск ищет "в лоб" точно подходящие слова или фразы.
Помимо этого способа, используя СУБД
Файлов задействовано много, и в процессе будут возникать моменты, когда будем прописывать то, чего ещё нет, дабы не прерываться.
щё одной немаловажной частью сайта является поиск. У пользователя должна быть возможность найти материал по запрошенному слову или фразе.
В Django есть простой механизм поиска с помощью фильтра
contains или icontains. Первый чувствителен к регистру, а второй нет.Например:
PostModel.objects.filter(full_body__icontains='поисковый запрос').Но такой поиск ищет "в лоб" точно подходящие слова или фразы.
Помимо этого способа, используя СУБД
PostgreSQL, открываются расширенные возможности поиска. Такие, как выдача результатов в зависимости от частоты встречаемого запроса в тексте или поиск по триграммному сходству.Файлов задействовано много, и в процессе будут возникать моменты, когда будем прописывать то, чего ещё нет, дабы не прерываться.
Представление и функционал поиска.
Начнём с представления для поиска и его непосредственного функционала.
Откроем файл
Код класса:
В теле класса пропишем поле
Далее создадим метод
В нём создадим переменную
Затем проверка на валидность формы. Если форма валидна, в переменную
Далее идёт код поиска, его рассмотрим чуть позже.
Затем, поскольку результатов может быть много, мы разбиваем их на страницы используя пагинацию.
Создаём переменную
Ниже возвращаем рендер страницы с результатами поиска. А вне блока
Теперь рассмотрим два варианта реализации поиска.
**Поиск по частоте упоминания в тексте или полнотекстовый поиск**
Код:
Сначала определяем поля для поиска с помощью `SearchVector`. В нашем случае, поиск будет производиться по полям 'title' и 'full_body'.
Затем создаем объект `SearchQuery`, который представляет собой запрос для поиска, передавая в него поисковый запрос.
Затем осуществляется фильтрация и сортировка результатов поиска:
Обращаясь к нашему собственному менеджеру постов, выдающему только опубликованные посты, методом
В итоге, результаты фильтруются по `search_query` и сортируются в порядке убывания ранга (
Поиск по сходству или триграммный поиск.
Код:
Начнём с представления для поиска и его непосредственного функционала.
Откроем файл
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`, где сходство больше 0.1. То есть, в результаты попадут только те посты, у которых сходство с запросом больше 0.1.
В итоге сортируем результаты в порядке схожести с запросом.
Для своего сайта я выбрал второй вариант.
Продолжение в следующем посте.
Пост на сайте.
Поддержать канал.
similarity находится результат применения `TrigramSimilarity` - Это функция, которая на основе алгоритма трехграмм вычисляет сходство между строками. В нашем случае функция применяется к полям 'full_body' и 'title'.Далее, как и в предыдущем коде используется собственный менеджер и метод
annotate.Затем происходит фильтрация результатов. Используется фильтрация по значению `similarity`, где сходство больше 0.1. То есть, в результаты попадут только те посты, у которых сходство с запросом больше 0.1.
В итоге сортируем результаты в порядке схожести с запросом.
Для своего сайта я выбрал второй вариант.
Продолжение в следующем посте.
Пост на сайте.
Поддержать канал.
Django 29.2 Добавляем поиск на сайт, продолжение
Форма поиска.
Напишем форму поиска. Для этого в директории приложения создадим новый файл
Внутри файла создадим класс
Пропишем поле
Ниже напишем метод
Код формы:
Форма поиска.
Напишем форму поиска. Для этого в директории приложения создадим новый файл
forms.py. Внутри файла создадим класс
SearchForm унаследованный от forms.Form.Пропишем поле
query. Поле будет являться символьным полем с максимальной длинной в 100 символов.Ниже напишем метод
clean_query. Данный метод будет очищать поисковый запрос от лишних пробелов внутри строки.Код формы:
from django import forms
class SearchForm(forms.Form):
query = forms.CharField(max_length=100,
widget=forms.TextInput(
attrs={
'class': 'form-control me-2 mx-1',
'placeholder': 'Что ищем?',
}
))
def clean_query(self):
query = self.cleaned_data['query']
cleaned_query = " ".join(query.split())
return cleaned_query
🔥1
Собственный менеджер для постов.
Нам также понадобится собственный менеджер, возвращающий только опубликованные посты.
Откроем файл
Внутри класса переопределим метод
Код менеджера:
Далее найдём модель поста и добавим новое поле, определяющее нашего менеджера -
Контекстный процессор формы поиска.
Для того чтобы форма поиска была доступна на всех страницах, её удобнее всего передавать через контекстные процессоры.
Откроем или создадим файл, если у вас его нет,
В файле напишем функцию
В теле функции сразу пишем возврат словаря, в котором будет ключ
Код:
Далее необходимо зарегистрировать функцию. Открываем файл
В ней находим список
Также не уходя из файла, в переменную
URL-маршрут для страницы поиска.
Откроем файл
Указывая по какому адресу будет открываться поиск, какое представление за него отвечает и какое имя будет у маршрута.
Блок поиска в шапке сайта.
Для добавления поиска в шапку сайта, откройте файл
Исправление пагинации для работы с поиском.
Поскольку у нас в запросе кроме номера страницы, ещё и переменная с поисковым запросом, стандартная пагинация работать не будет. Но исправление проблемы максимально простое.
Откроем файл
Например, так было:
А так должно быть:
Шаблон страницы поиска.
В шаблоне будем выводить поисковый запрос пользователя, результаты поиска и пагинацию.
Нам также понадобится собственный менеджер, возвращающий только опубликованные посты.
Откроем файл
models.py и после импортов создадим класс PostManager унаследованный от models.Manager.Внутри класса переопределим метод
get_queryset.Код менеджера:
class PostManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(status=PostModel.Status.PUBLISHED)
Далее найдём модель поста и добавим новое поле, определяющее нашего менеджера -
post_manager = PostManager()Контекстный процессор формы поиска.
Для того чтобы форма поиска была доступна на всех страницах, её удобнее всего передавать через контекстные процессоры.
Откроем или создадим файл, если у вас его нет,
context_processors.py.В файле напишем функцию
get_search_form принимающую request. В теле функции сразу пишем возврат словаря, в котором будет ключ
search_form, а значением форма.Код:
from blog import forms
def get_search_form(request):
return {'search_form': forms.SearchForm()}
Далее необходимо зарегистрировать функцию. Открываем файл
settings.py и находим переменную TEMPLATES.В ней находим список
context_processors и в него дописываем функцию - 'blog.context_processors.get_search_form',Также не уходя из файла, в переменную
INSTALLED_APPS добавим строку - 'django.contrib.postgres',.URL-маршрут для страницы поиска.
Откроем файл
urls.py приложения и добавим новую строку:path('search/', views.SearchPageView.as_view(), name='search_page'),Указывая по какому адресу будет открываться поиск, какое представление за него отвечает и какое имя будет у маршрута.
Блок поиска в шапке сайта.
Для добавления поиска в шапку сайта, откройте файл
header.html и пропишите в удобном для вас месте следующий код:<li class="nav-item">
<form class="d-flex" method="GET" action="{% url 'blog:search_page' %}">
{{ search_form.query }}
<button class="btn btn-primary search-btn" type="submit">Поиск</button>
</form>
</li>
Исправление пагинации для работы с поиском.
Поскольку у нас в запросе кроме номера страницы, ещё и переменная с поисковым запросом, стандартная пагинация работать не будет. Но исправление проблемы максимально простое.
Откроем файл
pagination.html и к каждой ссылке добавим проверку на наличие переменной query. Если она есть, то в адрес добавляется переменная с запросом.Например, так было:
<li class="page-item"><a class="page-link" href="?page={{ num_page }}">{{ num_page }}</a></li>А так должно быть:
<li class="page-item"><a class="page-link" href="?page={{ num_page }}{% if request.GET.query %}&query={{ request.GET.query }}{% endif %}">{{ num_page }}</a></li>Шаблон страницы поиска.
В шаблоне будем выводить поисковый запрос пользователя, результаты поиска и пагинацию.
Код шаблона:
Файлы к посту, можно получить в боте по коду: 159845
Пост на сайте.
Поддержать канал.
{% extends "blog/base.html" %}
{% block title %}{{ query }}{% endblock %}
{% block content %}
<section class="info">
<div class="container">
<h1>Результаты по запросу: {{ query }}</h1>
<hr> </div> </section>
<section class="posts">
{% if results %}
<div class="container">
<div class="row">
<div class="col-lg-9 col-sm-12 cat-block">
{% for post in results %}
<div class="row">
<div class="col-lg-4 col-sm-12 d-flex align-items-center">
<img src="{{ post.image.url }}" alt="{{ post.title }}" class="img-fluid">
</div> <div class="col-lg-8 col-sm-12">
<h2 class="head2"><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
<div class="mb-3">
<p class="lead"><a
href="{{ post.category.get_absolute_url }}">{{ post.category }}</a>
| {{ post.author }} | {{ post.publish | date:"d F Y" }} |
Просмотров: {{ post.views }}</p>
{% for tag in post.tags.all %}
<a href="{% url 'blog:tag_page' tag.slug %}"><span
class="badge bg-secondary">{{ tag.name }}</span></a>
{% endfor %}
</div>
{{ post.short_body | safe }}
</div>
</div> <hr class="m-3">
{% endfor %}
</div>
<div class="col-lg-3 col-sm-12">
<h2 class="mb-3">Ссылки</h2>
{% block social_links %}
{% include 'blog/index_page/social_links.html' %}
{% endblock %}
</div>
</div> <div class="col-12">
{% include "blog/modules/pagination.html" with page_obj=results %}
</div>
</div> {% else %}
<div class="container">
<h3>Нет результатов</h3>
</div> {% endif %}
</section>
{% endblock %}
На этом всё. Действий много, но они всё "точечные". Теперь на сайте есть работающий поиск.Файлы к посту, можно получить в боте по коду: 159845
Пост на сайте.
Поддержать канал.
👍3
Полезные инструменты - Certbot - бесплатный SSL-сертификат для сайта
В настоящее время даже думать не стоит о запуске сайта без оснащённого SSL-сертификатом. Более того, в скором времени сайты работающие на HTTP вовсе перестанут открываться в браузерах без "танцев с бубном" и десятка подписей, что вы осознаёте все риски.
Самый простой способ получить SSL-сертификат, это его купить у хостинг-провайдера либо в специализированном центре сертификации.
Существуют разные виды сертификатов, различающиеся лишь "уровнем доверия". В кавычках, потому, что по большому счёту это "плата за воздух" и "бренд", но это отдельная тема для дискуссии.
Нас же интересует бесплатный сертификат
В настоящее время даже думать не стоит о запуске сайта без оснащённого SSL-сертификатом. Более того, в скором времени сайты работающие на HTTP вовсе перестанут открываться в браузерах без "танцев с бубном" и десятка подписей, что вы осознаёте все риски.
Самый простой способ получить SSL-сертификат, это его купить у хостинг-провайдера либо в специализированном центре сертификации.
Существуют разные виды сертификатов, различающиеся лишь "уровнем доверия". В кавычках, потому, что по большому счёту это "плата за воздух" и "бренд", но это отдельная тема для дискуссии.
Нас же интересует бесплатный сертификат
Let's Encrypt. Сертификат выдаётся на 3 месяца. Если у вас небольшой сайт или страничка PET-проекта, этого достаточно на первое время.👍1