Evgenii Legotckoi
04 липня 2018 р. 12:38

Django - Урок 034. Як зробити пошук по декількох моделях даних

У попередніх статтях ми розглядали, як зробити пошук на сайті на сайті. А саме:

Але що якщо у вас більше, ніж один тип контенту. У вас можуть бути статті, коментарі, форум і повідомлення на форумі. Як тоді бути?

Якщо Ви хочете зробити все самостійно, без застосування сторонніх бібліотек, то тоді потрібно буде зробити пошук по всіх необхідних моделям і об'єднати результат. У мене зроблено точно також на сайті.


urls.py

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

Важливим моментів тут є те, що ми будемо обробляти get запити, щоб користувачі могли ділитися посиланнями з пошуковою видачею.

У файлі urls.py пропишемо маршрут для пошукової видачі

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

views.py

Припустимо у нас є кілька типів контенту:

  • Article
  • Comment
  • Topic
  • Post

Потрібно в поданні виконати пошук по всім видам контенту і об'єднати їх в один QuerySet і підготувати для пагінацію і видачі

  1. from itertools import chain
  2.  
  3. from django.shortcuts import render
  4. from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
  5. from django.views import View
  6.  
  7. from .models import Article, Comment, Topic, Post
  8.  
  9.  
  10. class ESearchView(View):
  11. template_name = 'search/index.html'
  12.  
  13. def get(self, request, *args, **kwargs):
  14. context = {}
  15.  
  16. q = request.GET.get('q')
  17. if q:
  18. query_sets = [] # Загальний QuerySet
  19.  
  20. # Шукаємо по всіх моделях
  21. query_sets.append(Article.objects.search(query=q))
  22. query_sets.append(Comment.objects.search(query=q))
  23. query_sets.append(Topic.objects.search(query=q))
  24. query_sets.append(Post.objects.search(query=q))
  25.  
  26. # і об'єднуємо видачу
  27. final_set = list(chain(*query_sets))
  28. final_set.sort(key=lambda x: x.pub_date, reverse=True) # виконуємо сортування
  29.  
  30. context['last_question'] = '?q=%s' % q
  31.  
  32. current_page = Paginator(final_set, 10)
  33.  
  34. page = request.GET.get('page')
  35. try:
  36. context['object_list'] = current_page.page(page)
  37. except PageNotAnInteger:
  38. context['object_list'] = current_page.page(1)
  39. except EmptyPage:
  40. context['object_list'] = current_page.page(current_page.num_pages)
  41.  
  42. return render(request=request, template_name=self.template_name, context=context)

Нюанс вище наведеного коду в тому, що ми об'єднуємо всі моделі даних, а також виконуємо їх сортування за датою. Щоб це було можливим, ми скористаємося можливостями мови програмування Python , а саме качиної типізацією. Сортування за датою стала можливою тому, що всі моделі даних мають поле дати публікації з однаковою назвою pub_date .

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

Також цікаво те, що objects всіх моделей даних, які представлені в даному коді мають один і той же метод search. Даний метод не є стандартним. Для його реалізації необхідно написати власний менеджер моделі і привласнити його полю objects .

ArticleManager

Розглянемо менеджер моделі статей. Успадковуємо його від базового менеджера моделей і визначимо метод search з логікою пошуку. Аналогічним чином потрібно буде прописати даний метод для всіх моделей, в яких буде виконуватися пошук.

  1. from django.db import models
  2. from django.db.models import Q
  3.  
  4. class ArticleManager(models.Manager):
  5. use_for_related_fields = True
  6.  
  7. def search(self, query=None):
  8. qs = self.get_queryset()
  9. if query:
  10. or_lookup = (Q(title__icontains=query) | Q(content__icontains=query))
  11. qs = qs.filter(or_lookup)
  12.  
  13. return qs

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

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

search/index.html

А для шаблону пошуку можна використовувати трохи модифікований шаблон з однією з перших статей з організації пошуку.

  1. {% load bootstrap34%}
  2. {% block page %}
  3. <h1>Поиск</h1>
  4. {% if object_list %}
  5. {% for object in object_list %}
  6. <div>
  7. <a href="{{ object.get_absolute_url }}">
  8. <h2>{{ object.itle }}</h2>
  9. </a>
  10. {{ object.content|safe }}
  11. <p><a class="btn btn-default btn-sm" href="{{ object.get_absolute_url }}">Читать далее</a></p>
  12. </div>
  13. {% endfor %}
  14. {% bootstrap_pagination object_list url=last_question %}
  15. {% else %}
  16. <p>Не найдено публикаций по вашему запросу<br>Попробуйте повторить запрос с другой формулировкой</p>
  17. {% endif %}
  18. {% endblock %}

Спеціально використовую в даному випадку знеособлений параметр object, який не має вказівки на конкретний тип контенту, щоб було ясно, що там може знаходитися будь-якій об'єкт контента. Головне, щоб були реалізовані всі методи у всіх моделей, які застосовуються в даному шаблоні.

Вам це подобається? Поділіться в соціальних мережах!

g
  • 09 липня 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
  1. Models, остальные похожие:
  2.  
  3. class PostManager(models.Manager):
  4. use_for_related_fields = True
  5. def search(self, query=None):
  6. qs = self.get_queryset()
  7. if query:
  8. or_lookup = (Q(title__icontains=query) | Q(text__icontains=query))
  9. qs = qs.filter(or_lookup)
  10. return qs
  11.  
  12. class Post(models.Model):
  13. title = models.CharField(max_length=40, verbose_name='Название')
  14. text = RichTextUploadingField(blank=True, null=True, verbose_name='Текст')
  15. image = models.ImageField(blank=True, upload_to=get_timestamp_path, verbose_name='Изображение')
  16. is_active = models.BooleanField(default=True, db_index=True, verbose_name='Выводить в списке?')
  17. created_at = models.DateTimeField(auto_now_add=True, verbose_name='Создано')
  18. published_date = models.DateTimeField(blank=True, null=True, verbose_name='Опубликовано')
  19. slug = models.SlugField(max_length=255, blank=True)
  20. objects = PostManager()
  21.  
  22. def delete(self, *args, **kwargs):
  23. for ap in self.postImage_set.all():
  24. ap.delete()
  25. super().delete(*args, **kwargs)
  26.  
  27. def publish(self):
  28. self.published_date = timezone.now()
  29. self.save()
  30.  
  31. def __str__(self):
  32. return self.title
  33.  
  34. def get_absolute_url(self):
  35. return '/post_list/%s/' % self.pk
  36.  
  37. class Meta:
  38. verbose_name_plural = 'Публикации'
  39. verbose_name = 'Публикация'
  40.  
  41.  
  42. Views:
  43. class SearchView(View):
  44. template_name = 'main/search.html'
  45.  
  46. def get(self, request, *args, **kwargs):
  47. context = {}
  48.  
  49. q = request.GET.get('q')
  50. if q:
  51. query_sets = [] # Общий QuerySet
  52.  
  53. # Ищем по всем моделям
  54. query_sets.append(Bb.objects.search(query=q))
  55. query_sets.append(Article.objects.search(query=q))
  56. query_sets.append(Post.objects.search(query=q))
  57.  
  58.  
  59. # и объединяем выдачу
  60. final_set = list(chain(*query_sets))
  61. final_set.sort(key=lambda x: x.created_at, reverse=True) # Выполняем сортировку
  62.  
  63. context['last_question'] = '?q=%s' % query_sets
  64.  
  65. paginator = Paginator( final_set, 2)
  66.  
  67. page = request.GET.get('page')
  68.  
  69. try:
  70. context['object_list'] = paginator.page(page)
  71. except PageNotAnInteger:
  72. context['object_list'] = paginator.page(1)
  73. except EmptyPage:
  74. context['object_list'] = paginator.page(paginator.num_pages)
  75. return render(request=request, template_name=self.template_name, context=context )
  76.  
  77.  
  78.  
  79.  
  80. Шаблон:
  81. {% extends "layout/basic.html" %}
  82. {% load thumbnail %}
  83. {% load static %}
  84. {% load bootstrap4 %}
  85.  
  86. {% block content %}
  87.  
  88. <h1>Поиск</h1>
  89. {% if object_list %}
  90. {% for object in object_list %}
  91.  
  92. <div>
  93. <a href="{{ object.get_absolute_url }}">
  94. <h2>{{ object.title }}</h2>
  95. </a>
  96. {{ object.text|safe }}
  97. <p><a class="btn btn-default btn-sm" href="{{ object.get_absolute_url }}">Читать далее</a></p>
  98. </div>
  99.  
  100. {% endfor %}
  101.  
  102. {% bootstrap_pagination object_list url=last_question %}
  103.  
  104. {% else %}
  105. <p>Не найдено публикаций по вашему запросу<br>Попробуйте повторить запрос с другой формулировкой</p>
  106. {% endif %}
  107. {% endblock %}
  108.  
  109.  
  110. urls:
  111. path('search/', views.SearchView.as_view(), name='search'),
  112.  
  113.  
  114. кнопка:
  115.  
  116. <form class="navbar-form navbar-left m-2 p-4" action="{% url 'main:search' %}">
  117. <div class="input-group">
  118. <input id="search" name="q" type="text" class="form-control" placeholder="Найти">
  119. <div class="input-group-btn">
  120. <button class="btn btn-success" type="submit">
  121. <i class="fas fa-search"></i>
  122. </button>
  123. </div>
  124. </div>
  125. </form>
Evgenii Legotckoi
  • 14 травня 2020 р. 01:50

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

  1. context['last_question'] = '?q=%s' % q
Evgenii Legotckoi
  • 14 травня 2020 р. 02:13
  • (відредаговано)

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

S
  • 14 травня 2020 р. 02:18

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

Evgenii Legotckoi
  • 14 травня 2020 р. 02:26

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

S
  • 14 травня 2020 р. 22:58

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

  1. <ul class="pagination">
  2. {% if object_list.has_previous %}
  3. <li class="page-item"><a class="page-link" href="?q={{ object_list.previous_page_number }}">&lt;</a></li>
  4. <li class="page-item"><a class="page-link" href="?q={{ object_list.previous_page_number }}">{{ object_list.previous_page_number }}</a></li>
  5. {% endif %}
  6.  
  7. <li class="page-item active"><a class="page-link" href="?q={{ object_list.number }}">{{ object_list.number }}</a>
  8. </li>
  9.  
  10. {% if object_list.has_next %}
  11. <li class="page-item"><a class="page-link" href="?q={{ object_list.next_page_number }}">{{ object_list.next_page_number }}</a></li>
  12. <li class="page-item"><a class="page-link" href="?q={{ object_list.next_page_number }}">&gt;</a></li>
  13. {% endif %}
  14. </ul>
Evgenii Legotckoi
  • 15 травня 2020 р. 13:08

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

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

  1. 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
По поводу формы пагинатора, пробовал отключать все дополнительные стили, все также вылезает ошибка. Подумаю еще над этим, главное что есть рабочий вариант.
Спасибо за подсказку.

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…