Evgenii Legotckoi
Evgenii Legotckoi4 июля 2018 г. 12:38

Django - Урок 034. Как сделать поиск по нескольким моделям данных

Содержание

В предыдущих статьях мы рассмотрели, как сделать поиск по сайту на сайте. А именно:

Но что, если у вас более одного типа контента. Вы можете иметь статьи, комментарии, форум и сообщения на форуме. Как тогда быть?

Если вы хотите сделать все сами, без использования сторонних библиотек, то вам нужно будет сделать поиск по всем нужным моделям и объединить результат. Я сделал точно так же на сайте.


urls.py

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

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

В файле urls.py пропишем маршрут для поиска

app_name = 'home'
urlpatterns = [
    path('search/', views.SearchView.as_view(), name='search'),
]

views.py

Допустим, у нас есть несколько типов контента:

  • Article
  • Comment
  • Topic
  • Post

Нужно в представлении выполнить поиск по всем видам контента и объединить их в один QuerySet и подготовить к пагинации и выдаче

from itertools import chain

from django.shortcuts import render
from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
from django.views import View

from .models import Article, Comment, Topic, Post


class ESearchView(View):
    template_name = 'search/index.html'

    def get(self, request, *args, **kwargs):
        context = {}

        q = request.GET.get('q')
        if q:
            query_sets = []  # Total QuerySet

            # Searching for all models
            query_sets.append(Article.objects.search(query=q))
            query_sets.append(Comment.objects.search(query=q))
            query_sets.append(Topic.objects.search(query=q))
            query_sets.append(Post.objects.search(query=q))

            # and combine results
            final_set = list(chain(*query_sets))
            final_set.sort(key=lambda x: x.pub_date, reverse=True)  # Sorting

            context['last_question'] = '?q=%s' % q

            current_page = Paginator(final_set, 10)

            page = request.GET.get('page')
            try:
                context['object_list'] = current_page.page(page)
            except PageNotAnInteger:
                context['object_list'] = current_page.page(1)
            except EmptyPage:
                context['object_list'] = current_page.page(current_page.num_pages)

        return render(request=request, template_name=self.template_name, context=context)

Нюанс приведенного выше кода в том, что мы объединяем все модели данных, а также сортируем их по дате. Чтобы это стало возможным, мы будем использовать возможности языка программирования Python, а именно утиную типизацию. Сортировка по дате стала возможной благодаря тому, что во всех моделях данных есть поле даты публикации с одинаковым названием pub_date.

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

Также интересно то, что объекты всех моделей данных, которые представлены в этом коде, имеют один и тот же метод поиска. Этот метод не является стандартным. Для его реализации нужно написать свой менеджер моделей и назначить его на поле объектов.

ArticleManager

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

from django.db import models
from django.db.models import Q

class ArticleManager(models.Manager):
    use_for_related_fields = True

    def search(self, query=None):
        qs = self.get_queryset()
        if query:
            or_lookup = (Q(title__icontains=query) | Q(content__icontains=query))
            qs = qs.filter(or_lookup)

        return qs

Установка менеджера в модель

class Article(models.Model):
    objects = ArticleManager()

search/index.html

А для шаблона поиска можно использовать немного измененный шаблон из одной из первых статей по организации поиска.

{% load bootstrap34%}
{% block page %}
    <h1>Поиск</h1>
    {% if object_list %}
        {% for object in object_list %}
            <div>
                <a href="{{ object.get_absolute_url }}">
                    <h2>{{ object.itle }}</h2>
                </a>
                {{ object.content|safe }}
                <p><a class="btn btn-default btn-sm" href="{{ object.get_absolute_url }}">Читать далее</a></p>
            </div>
        {% endfor %}
        {% bootstrap_pagination object_list url=last_question %}
    {% else %}
        <p>Не найдено публикаций по вашему запросу<br>Попробуйте повторить запрос с другой формулировкой</p>
    {% endif %}
{% endblock %}

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

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

g
  • 9 июля 2018 г. 15:00

Исправьте код в views.py, пропущен импорт chain из itertools.

Evgenii Legotckoi
  • 10 июля 2018 г. 22:49

Спасибо! Исправил.

S
  • 13 мая 2020 г. 16:36

Добрый день, небольшая проблемка с пагинацией, отображается информация только на первой странице при переходе на другую - пустая страница, views, models.Manager, шаблон, все сделал как в данном уроке, подставил только свои значения. Подскажите, в чем может быть проблема?
Спасибо.

Evgenii Legotckoi
  • 13 мая 2020 г. 16:41

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

S
  • 13 мая 2020 г. 17:28
Models, остальные похожие:

class PostManager(models.Manager):
    use_for_related_fields = True
    def search(self, query=None):
        qs = self.get_queryset()
        if query:
            or_lookup = (Q(title__icontains=query) | Q(text__icontains=query))
            qs = qs.filter(or_lookup)
        return qs

class Post(models.Model):
    title = models.CharField(max_length=40, verbose_name='Название')
    text = RichTextUploadingField(blank=True, null=True, verbose_name='Текст')
    image = models.ImageField(blank=True, upload_to=get_timestamp_path, verbose_name='Изображение')
    is_active = models.BooleanField(default=True, db_index=True, verbose_name='Выводить в списке?')
    created_at = models.DateTimeField(auto_now_add=True, verbose_name='Создано')
    published_date = models.DateTimeField(blank=True, null=True, verbose_name='Опубликовано')
    slug = models.SlugField(max_length=255, blank=True)
    objects = PostManager()

    def delete(self, *args, **kwargs):
        for ap in self.postImage_set.all():
            ap.delete()
        super().delete(*args, **kwargs)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return '/post_list/%s/' % self.pk

    class Meta:
        verbose_name_plural = 'Публикации'
        verbose_name = 'Публикация'


Views:
class SearchView(View):
    template_name = 'main/search.html'

    def get(self, request, *args, **kwargs):
        context = {}

        q = request.GET.get('q')
        if q:
            query_sets = []  # Общий QuerySet

            # Ищем по всем моделям
            query_sets.append(Bb.objects.search(query=q))
            query_sets.append(Article.objects.search(query=q))
            query_sets.append(Post.objects.search(query=q))


            # и объединяем выдачу
            final_set = list(chain(*query_sets))
            final_set.sort(key=lambda x: x.created_at, reverse=True)  # Выполняем сортировку

            context['last_question'] = '?q=%s' % query_sets

            paginator = Paginator( final_set, 2)

            page = request.GET.get('page')

            try:
                context['object_list'] = paginator.page(page)
            except PageNotAnInteger:
                context['object_list'] = paginator.page(1)
            except EmptyPage:
                context['object_list'] = paginator.page(paginator.num_pages)
        return render(request=request, template_name=self.template_name, context=context )




Шаблон:
{% extends "layout/basic.html" %}
{% load thumbnail %}
{% load static %}
{% load bootstrap4 %}

{% block content %}

 <h1>Поиск</h1>
    {% if object_list %}
        {% for object in object_list %}

            <div>
                <a href="{{ object.get_absolute_url }}">
                    <h2>{{ object.title }}</h2>
                </a>
                {{ object.text|safe }}
                <p><a class="btn btn-default btn-sm" href="{{ object.get_absolute_url }}">Читать далее</a></p>
            </div>

        {% endfor %}

        {% bootstrap_pagination object_list  url=last_question %}

    {% else %}
        <p>Не найдено публикаций по вашему запросу<br>Попробуйте повторить запрос с другой формулировкой</p>
    {% endif %}
{% endblock %}


urls:
path('search/', views.SearchView.as_view(), name='search'),


кнопка:

<form class="navbar-form navbar-left m-2 p-4" action="{% url 'main:search' %}">
            <div class="input-group">
                <input id="search" name="q" type="text" class="form-control" placeholder="Найти">
                <div class="input-group-btn">
                    <button class="btn btn-success" type="submit">
                        <i class="fas fa-search"></i>
                    </button>
                </div>
            </div>
        </form>
Evgenii Legotckoi
  • 14 мая 2020 г. 1:50

Опечатка в статье. Напишите так

context['last_question'] = '?q=%s' % q
Evgenii Legotckoi
  • 14 мая 2020 г. 2:13
  • (ред.)

И на старуху бывает проруха. Спасибо за вопрос.
Если бы не просмотрел ваш код, то скорее всего не заметил бы опечатку

S
  • 14 мая 2020 г. 2:18

Спасибо, Евгений, все заработало. Хороший сайт.

Evgenii Legotckoi
  • 14 мая 2020 г. 2:26

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

S
  • 14 мая 2020 г. 22:58

Здравствуйте, небольшой вопрос,
при добавлении формы пагинатора левая крайняя кнопка назад "<<" постоянно смещена вверх, относительно ряда цифр, не могу ее никак выровнять, в чем может быть проблема?
{% bootstrap_pagination object_list url=last_question %}
Хотел установить пагинатор прописав код, но немного запутался с адресами между страниц.
Подскажите, как правильно прописать адреса.
Спасибо.

<ul class="pagination">
            {% if object_list.has_previous %}
            <li class="page-item"><a class="page-link" href="?q={{ object_list.previous_page_number }}">&lt;</a></li>
            <li class="page-item"><a class="page-link" href="?q={{ object_list.previous_page_number }}">{{ object_list.previous_page_number }}</a></li>
            {% endif %}

            <li class="page-item active"><a class="page-link" href="?q={{ object_list.number }}">{{ object_list.number }}</a>
            </li>

            {% if object_list.has_next %}
            <li class="page-item"><a class="page-link" href="?q={{ object_list.next_page_number }}">{{ object_list.next_page_number }}</a></li>
            <li class="page-item"><a class="page-link" href="?q={{ object_list.next_page_number }}">&gt;</a></li>
            {% endif %}
        </ul>
Evgenii Legotckoi
  • 15 мая 2020 г. 13:08

У вас есть ещё какие-то кастомные стили, которые могут влиять на "<<", поскольку у меня такой проблемы не наблюдалось.

Ну по идее, это как-то тако должно выглядеть

href="?q={{q}}&page={{ object_list.previous_page_number }}"
S
  • 15 мая 2020 г. 15:03

Добрый день, Евгений,
сработал вот такой вариант:
href="?q={{ question }}&page={{ object_list.previous_page_number }}"
и во views добавил:
context['question'] = q
По поводу формы пагинатора, пробовал отключать все дополнительные стили, все также вылезает ошибка. Подумаю еще над этим, главное что есть рабочий вариант.
Спасибо за подсказку.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Lz

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:70баллов,
  • Очки рейтинга1
РК

Qt - Тест 001. Сигналы и слоты

  • Результат:84баллов,
  • Очки рейтинга4
ВМ

C++ - Тест 001. Первая программа и типы данных

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr9 февраля 2024 г. 2:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 9:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 18:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 16:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 5:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
NSProject
NSProject23 июня 2024 г. 20:48
добавить qlineseries в функции А куда собстаенно делся Евгений раз на сайте такой бордак творится?
BlinCT
BlinCT5 мая 2024 г. 12:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
Evgenii Legotckoi
Evgenii Legotckoi2 мая 2024 г. 21:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 апреля 2024 г. 11:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 апреля 2024 г. 12:46
Clipboard Как скопировать окно целиком в clipb?

Следите за нами в социальных сетях