Создание сайта на Django: Урок 40, создание поисковых алгоритмов по статьям
avatar
7 | (offline)
❤️‍🔥Notehunter Developer
Добавлено:
Категория: Руководства «Django»
Комментариев: 5

Встроенная система Django ORM уже предоставляет некоторые фильтры для поиска, но они работают не так, как хотелось бы на деле.

Поэтому мы будем использовать полнотекстовый поисковых движок из модуля django.contrib.postgres в PostgreSQL, который дает нам отличные инструменты для реализации поиска на сайте.

Создание представления поискового алгоритма

Я буду создавать представление в модуле blog, в файле views/articles.py

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

modules/blog/views/articles.py

from django.contrib.postgres.search import SearchVector, SearchQuery, SearchRank

class ArticleSearchResultView(ListView):
    """
    Реализация поиска статей на сайте
    """
    model = Article
    context_object_name = 'articles'
    paginate_by = 10
    allow_empty = True
    template_name = 'modules/blog/articles/article-list.html'

    def get_queryset(self):
        query = self.request.GET.get('do')
        search_vector = SearchVector('full_description', weight='B') + SearchVector('title', weight='A')
        search_query = SearchQuery(query)
        return (
            self.model.objects.annotate(rank=SearchRank(search_vector, search_query))
                .filter(rank__gte=0.3)
                .order_by('-rank')
        )

    def get_context_data(self, *, object_list=None, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = 'Результаты поиска'
        context['do'] = f"do={self.request.GET.get('value')}&"
        return context

Пояснения:

  • В методе get_queryset получаем данные запроса из шаблона.
  • Создаем вектор SearchVector поиска по полному описанию и названию, присваиваем им символы А и В, что позволяет создать поиск по совпадению для двух полей.
  • Пропускаем запрос через SearchQuery, который позволит сортировать результаты по релевантности.
  • PostgreSQL предоставляет функцию ранжирования, которая сортирует результаты в зависимости от того, как часто появляется текст запроса и насколько они близки друг к другу.

Далее добавим в urls.py наше представление:

modules/blog/urls.py

from django.urls import path, include

from modules.blog.views import ArticleListView, ArticleDetailView, ArticleByCategoryListView, ArticleCreateView, \
    ArticleUpdateView, ArticleDeleteView, CommentCreateView, ArticleByTagListView, ArticleSearchResultView

urlpatterns = [
    path('', ArticleListView.as_view(), name='home'),
    path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
    path('articles/', include([
        path('', ArticleListView.as_view(), name='article-list'),
        path('create/', ArticleCreateView.as_view(), name='article-create-view'),
        path('tags/<str:tag>/', ArticleByTagListView.as_view(), name='article-list-by-tags'),
        path('<int:pk>/comments/create/', CommentCreateView.as_view(), name='comment-create-view'),
        path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
        path('<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
        path('<str:slug>/delete/', ArticleDeleteView.as_view(), name='article-delete-view'),
    ])),
    path('search/', ArticleSearchResultView.as_view(), name='search'),
]

Перейдем в наш шаблон navbar.html и создадим возможность поиска через форму:

templates/navbar.html

Было

<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
	<input type="search" class="form-control" placeholder="Search..." aria-label="Search">
</form>

Сделали:

<form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search" method="get" action="{% url 'search' %}">
	<input type="search" class="form-control" placeholder="Search..." aria-label="Search" name='do' autocomplete="off" id="search">
</form>

Пояснение:

  • Добавил метод в форму (GET)
  • Добавил action="{% url 'search' %}
  • Добавил в инпут: name='do' autocomplete="off" id="search"

Попробуем наш поиск в деле, в поисковую строку введу "Python" и нажму Enther:

А если введу "Тестовая

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

Комментарии к статье 5
  • nikollia
    29 октября 2022 г. 1:31

    Здравствуйте! У меня почему-то не получается реализовать поиск. Все сделала как показано в вашем уроке. При запросе, появляется ошибка, что страница не найдена, что вообще странно ...

    Page not found (404) No Article found matching the query Request Method: GET Request URL: http://127.0.0.1:8000/blog/search/?do=django Raised by: blog.views.articles.ArticleDetailView

    У меня Mac и бд postgresql устанавливала через Postgres и psycopg2-binary. Может это как-то повлияло на эту ошибку?

    Заранее огромное спасибо!

    • Razilator
      29 октября 2022 г. 1:57

      nikollia, в воскресенье буду дома, сейчас в МСК, рассмотрю подробно и помогу вам в решении вашей проблемы.

    • Razilator
      31 октября 2022 г. 6:52

      nikollia, странно, сделал также как у себя в уроке, и все работает даже на самом примитивном уровне... Я тоже поставил psycopg2-binary, и все также работает.

      Результат

      Может вы не в то место вставили саму ссылку? Или у вас глобально все начинается с префикса /blog/

      Просто как таковой, ошибку выдает именно то, что такую страничку вроде найти не может. И попробуйте контроллер с search поднять выше всех url'ов, или же наоборот, вниз самый опустить, и посмотреть, будет ли работать, просто возможно он кушает другое значение и /search/ не воспринимает как отдельный контроллер из-за приоритетов, а кушает его как slug для другого контроллера.

      P.S. Посмотрел подробно. Да, точно, у вас скорее всего приоритеты search - ищется как slug какого-то поста, из контроллера ArticleDetailView. Поместите search в самый верх. Ибо наш контроллер с Search называется ArticleSearchResultView, а ловит search - ArticleDetailView, поэтому и выдает ошибку, что страница не найдена. Надо сделать как-то так, только в Вашей интерпретации, Анастасия:

      urlpatterns = [
          path('', ArticleListView.as_view(), name='home'),
          path('search/', ArticleSearchResultView.as_view(), name='search'),
          path('cat/<int:pk>/<str:slug>/', ArticleByCategoryListView.as_view(), name='article-by-cat'),
          path('articles/', include([
              path('', ArticleListView.as_view(), name='article-list'),
              path('create/', ArticleCreateView.as_view(), name='article-create-view'),
              path('tags/<str:tag>/', ArticleByTagListView.as_view(), name='article-list-by-tags'),
              path('<int:pk>/comments/create/', CommentCreateView.as_view(), name='comment-create-view'),
              path('<str:slug>/', ArticleDetailView.as_view(), name='article-detail'),
              path('<str:slug>/update/', ArticleUpdateView.as_view(), name='article-update-view'),
              path('<str:slug>/delete/', ArticleDeleteView.as_view(), name='article-delete-view'),
          ])),
      ]
      
      • nikollia
        1 ноября 2022 г. 18:12

        Razilator, прошу прощение да долгий ответ. Да, ошибка была именно в расположении. Теперь все работает корректно 👍🏻 Огромное вам спасибо, за помощь!! 😊👍🏻🙏🏻

        • Razilator
          1 ноября 2022 г. 18:22

          nikollia, не за что, рад был помочь, Анастасия. 😌

Форма добавления комментария (необходима регистрация)