Evgenii Legotckoi
Evgenii Legotckoi4 июля 2018 г. 2: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 г. 5:00

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

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

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

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

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

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

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

S
  • 13 мая 2020 г. 7: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
  • 13 мая 2020 г. 15:50

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

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

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

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

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

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

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

S
  • 14 мая 2020 г. 12: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 г. 3:08

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

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

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

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
г
  • ги
  • 23 апреля 2024 г. 12:51

C++ - Тест 005. Структуры и Классы

  • Результат:41баллов,
  • Очки рейтинга-8
l
  • laei
  • 23 апреля 2024 г. 6:19

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

  • Результат:10баллов,
  • Очки рейтинга-10
l
  • laei
  • 23 апреля 2024 г. 6:17

C++ - Тест 003. Условия и циклы

  • Результат:50баллов,
  • Очки рейтинга-4
Последние комментарии
k
kmssr8 февраля 2024 г. 15:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко4 февраля 2024 г. 22:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 7:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 5:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 18:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
Gar22 апреля 2024 г. 2:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 апреля 2024 г. 4:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 3:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев13 апреля 2024 г. 23:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь
f
fastrex4 апреля 2024 г. 1:47
Вернуть старое поведение QComboBox, не менять индекс при resetModel Добрый день! У нас много проектов в которых используется QComboBox, в версии 5.5.1, когда модель испускает сигнал resetModel, currentIndex не менялся. В версии 5.15 при resetModel происходит try…

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