В данном руководстве мы освоим базовый поиск по сайту Django и затронем способы улучшить его с более продвинутыми возможностями.
Полный исходный код можно найти на GitHub.
Для начала, давайте создадим новый проект Django (перейдите сюда, если нужна помощь). В вашей командной строке, введите следующие команды для установки последней версии при помощи Pipenv, создайте проект под названием citysearch_project
, настройте внутреннюю базу данных через migrate
и запустите локальный веб сервер при помощи runserver
.
1 2 3 4 5 |
$ pipenv install django==2.2.1 $ pipenv shell $ django-admin startproject citysearch_project . $ python manage.py migrate $ python manage.py runserver |
Если вы перейдете на http://127.0.0.1:8000/
, вы увидите приветствие Django, которое подтверждает, что все настроено правильно. Локальный сервер не выражает все моменты реальной работы сайта на сервере, можете ознакомиться со списком хостингов https://hostinghub.ru/top/vds на которых вы можете запустить полноценный сайт на Python.
Создаем приложение Cities в Django
Теперь мы создадим одно приложение под названием cities
для хранения списка названий городов. Мы осознанно не будем выходить за рамки простых основ. Остановите локальный сервер при помощи Ctrl+C
и используйте команду startapp
для создания нашего нового приложения.
1 |
$ python manage.py startapp cities |
Затем обновите INSTALLED_APPS
внутри нашего файла settings.py
, чтобы сообщить Django о новом приложении.
1 2 3 4 5 |
# citysearch_project/settings.py INSTALLED_APPS = [ ... 'cities.apps.CitiesConfig', # new ] |
Теперь перейдем к моделям. Мы назовем нашу единственную модель City
. В ней будет два поля: name
и state
. Так как админка Django по умолчанию будет менять имя приложения во множественном числе на Citys
, мы также настроим verbose_name_plural
. И наконец настроим __str__
для отображения названия города.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# cities/models.py from django.db import models class City(models.Model): name = models.CharField(max_length=255) state = models.CharField(max_length=255) class Meta: verbose_name_plural = "cities" def __str__(self): return self.name |
Отлично, все настроено. Мы можем создать файл миграции для этого изменения, затем добавить его в нашу базу данных через migrate
.
1 2 |
$ python manage.py makemigrations cities $ python manage.py migrate |
Есть несколько способов наполнить базу данных, но самый простой, на мой взгляд, это через admin. Создайте аккаунт суперпользователя, чтобы мы смогли зайти в админку.
1 |
$ python manage.py createsuperuser |
Теперь нам нужно обновить cities/admin.py
для отображения нашего приложения внутри админки.
1 2 3 4 5 6 7 8 9 |
# cities/admin.py from django.contrib import admin from .models import City class CityAdmin(admin.ModelAdmin): list_display = ("name", "state",) admin.site.register(City, CityAdmin) |
Еще раз запустите сервер при помощи python manage.py runserver
и направьтесь в админку по http://127.0.0.1:8000/admin
, затем зайдите в свой аккаунт суперпользователя.
Нажмите на раздел cities
и добавьте несколько записей. Здесь видно четыре моих примера.
Домашняя страница и страница выдачи поиска Django
У нас есть заполненная база данных, однако все еще есть несколько шагов, которые нужно выполнить, перед тем как она может быть отображена на нашем сайте Django. В конце концов, нам нужна только домашняя страница и страница выдачи поиска. Каждой странице нужен надлежащий вид, url и шаблон. Порядок, в котором мы их будем создавать не принципиальный. Все должно быть на сайте для правильной работы.
В целом, я предпочитаю начинать с URL-ов, добавить views
, и в конце создать шаблоны, чем мы и займемся.
Сначала нам нужно добавить путь URL для нашего приложения, это можно сделать, импортировав include
и настроив путь к нему.
1 2 3 4 5 6 7 8 |
# citysearch_project/urls.py from django.contrib import admin from django.urls import path, include # new urlpatterns = [ path('admin/', admin.site.urls), path('', include('cities.urls')), # new ] |
Далее, нам нужнен файл urls.py
внутри приложения cities
, однако Django не создает такой для нас по команде startapp
. Не нужно беспокоиться, мы можем создать его в командной строке. Останавливаем сервер при помощи Ctrl+C
, если он еще работает.
1 |
$ touch cities/urls.py |
Внутри этого файла мы импортируем еще не созданные представления (views
) для каждой HomePageView
и SearchResultsView
, и указать путь к каждому из них. Обратите внимание на то, что мы указываем опциональное название URL для каждого из них.
Вот так это будет выглядеть:
1 2 3 4 5 6 7 8 9 |
# cities/urls.py from django.urls import path from .views import HomePageView, SearchResultsView urlpatterns = [ path('search/', SearchResultsView.as_view(), name='search_results'), path('', HomePageView.as_view(), name='home'), ] |
В третьих, нам нужно настроить наши два представления (views
). Домашняя страница будет простым шаблоном с итоговой поисковой строкой. Для Django отлично подойдет TemplateView
для этой цели. Страница поисковой выдачи упорядочит необходимые результаты, что хорошо ложится под ListView
.
1 2 3 4 5 6 7 8 9 10 11 12 |
# cities/views.py from django.views.generic import TemplateView, ListView from .models import City class HomePageView(TemplateView): template_name = 'home.html' class SearchResultsView(ListView): model = City template_name = 'search_results.html' |
Последний шаг — наши шаблоны. Мы можем добавить шаблоны внутри нашего приложения cities
, однако я нашел более простой подход, а именно — создание папку проектных шаблонов.
Создайте папку с шаблонами и затем оба шаблона: home.html
и search_results.html
.
1 2 3 |
$ mkdir templates $ touch templates/home.html $ touch templates/search_results.html |
Обратите внимание на то, что нам также нужно обновить наш settings.py
, чтобы указать Django на проектную папку с шаблонами. Это вы можете найти в разделе TEMPLATES
.
1 2 3 4 5 6 7 8 |
# citysearch_project/settings.py TEMPLATES = [ { ... 'DIRS': [os.path.join(BASE_DIR, 'templates')], # new ... } ] |
Домашная страница выведет только заголовок.
1 2 |
<!-- templates/home.html --> <h1>HomePage</h1> |
Запустите веб сервер еще раз при помощи python manage.py runserver
. Теперь мы можем увидеть домашнюю страницу на http://127.0.0.1:8000/
.
Теперь, для страницы поисковой выдачи, которая будет выполнять цикл на object_list
, вернется имя от контекстного объекта ListView
. Затем мы выведем name
и state
для каждой записи из базы данных.
1 2 3 4 5 6 7 8 9 10 |
<!-- templates/search_results.html --> <h1>Search Results</h1> <ul> {% for city in object_list %} <li> {{ city.name }}, {{ city.state }} </li> {% endfor %} </ul> |
Все готово! Наша страница поисковой выдачи доступна на http://127.0.0.1:8000/search/
.
Формы и наборы запросов в Django
В конечном итоге базовая реализация поиска сводится к форме, которая передает пользовательский запрос — сам фактический поиск — и затем к набору запросов, который будет фильтровать результаты на основе этого запроса.
Мы можем начать с любого из них, но мы начнем с настройки фильтрации, после чего перейдем к форме.
Базовая фильтрация запросов в Django
В Django, QuerySet
используется для фильтрации выдачи из модели базы данных. В данный момент, наша модель City
выводит все свое содержимое. В итоге нам нужно ограничить страницу поисковой выдачи для фильтрации выведенных результатов, на основании поискового запроса от пользователя.
Есть несколько способов настроить набор запросов, и фактически возможно выполнить фильтрацию через менеджер самой модели, однако, чтобы сохранять простоту решений, мы можем добавить фильтр всего в одну строку. Давайте сделаем это.
Здесь мы обновляем метод queryset
из ListView
и добавляем фильтр, так что возвращается только город под названием Бостон
. В итоге, мы заменим это переменной, которая представляет пользовательский поисковый запрос.
1 2 3 4 5 |
# cities/views.py class SearchResultsView(ListView): model = City template_name = 'search_results.html' queryset = City.objects.filter(name__icontains='Boston') # новый |
Обновите страницу поисковой выдачи и вы увидите, что отображается только Бостон
.
Также можно настроить queryset
, переопределив метод get_queryset()
, для изменения списка выданных городов. Явного преимущества в этом для нас нет, но этот подход мне кажется более гибким, чем просто указать атрибуты набора запросов.
1 2 3 4 5 6 7 8 |
# cities/views.py ... class SearchResultsView(ListView): model = City template_name = 'search_results.html' def get_queryset(self): # новый return City.objects.filter(name__icontains='Boston') |
Большую часть времени, встроенных методов filter()
, all()
, get()
, или exclude()
из QuerySet
будет достаточно. Однако есть очень надежный и детализированный API QuerySet.
Объекты Q в Django
Использование filter()
— эффективно, с ним даже можно связать фильтры вместе. Однако, вам могут понадобиться более сложные запросы, такие как ИЛИ (OR)
. В таких случаях приходит время объектов Q.
Вот пример того, где мы настраиваем фильтр на поиск результата, который совпадает с названием города Бостон
, или название штата, которое содержит аббревиатуру NY
. Это также просто, как импорт Q
вверху файла, и затем слегка поменять наш существующий запрос
1 2 3 4 5 6 7 8 9 10 11 12 |
# cities/views.py from django.db.models import Q # новый ... class SearchResultsView(ListView): model = City template_name = 'search_results.html' def get_queryset(self): # новый return City.objects.filter( Q(name__icontains='Boston') | Q(state__icontains='NY') ) |
Обновите вашу страницу поисковой выдачи, чтобы увидеть результат.
Теперь, вернемся к нашей HTML-форме поиска для замены текущих прописанных значений переменными поискового запроса.
Формы для ввода данных на сайте в Django
По сути, веб формы — это просто: они берут ввод пользователя и направляют его в URL либо через метод GET
, либо через POST
. Однако на практике, это фундаментальное поведение веба может быть монструозно сложным.
Первая проблема — это отправка данных формы: куда на самом деле идут данные, и как мы их будет обрабатывать? Не говоря уже о множественных проблемах с безопасностью, когда вы разрешаете пользователям отправлять данные на веб-сайт.
Существует только два варианта того, как отправлять форму: либо через HTTP метод GET
, либо через POST
.
POST
связывает данные формы, кодирует их для передачи, отправляет их на сервер и затем получает ответ. Любой запрос, который меняет состояние базы данных (создает, редактирует, или удаляет данные) — должен использовать POST
.
GET
связывает данные формы в строку, которая вносится в URL. GET
должен быть использован для такого запроса, который не влияет на состояние приложения, например — поиск, где ничего внутри базы данных не меняется. Мы просто выполняем отфильтрованный просмотр списка.
Если вы взгляните на URL после поиска в гугле, вы увидите свой поисковый запрос в самом URL страницы результатов поиска ?q=
.
Для дополнительной информации, Mozilla предоставляет подробные руководства как для отправки данных из формы, так и валидации форм данных, с которыми стоит ознакомиться, если вы не владеете основами.
Поисковая форма для сайта на Django
Впрочем, для наших целей, мы можем создать базовую форму поиска к существующей домашней странице уже сейчас. Вот так это выглядит. Мы рассмотрим каждую часть примера ниже.
1 2 3 4 5 6 |
<!-- templates/home.html --> <h1>HomePage</h1> <form action="{% url 'search_results' %}" method="get"> <input name="q" type="text" placeholder="Search..."> </form> |
Для формы, параметр action
определяет, куда направлять пользователя после нажатия на кнопку поиска. Мы используем URL
нашей страницы поисковой выдачи. Затем мы определим использование GET
в качестве нашего метода.
Пример кода из данной статьи будет работать и на полноценном сервере, можете выбрать хостинг для проектов на Python которые поддерживают фреймворк Django и запустить свой собственный сайт на Python в интернете.
Внутри нашего одиночного ввода возможно иметь несколько вводов, или добавить кнопку по желанию. Мы назовем ее q
, к чему мы и сошлемся далее. Определяем type
как text
. Затем добавляем значение к placeholder
для запроса пользователя.
И все! Теперь, попробуйте ввести запрос на главной странице, например — san diego
.
После нажатия кнопки Enter, вас перенаправит на страницу поисковой выдачи. Обратите внимание на то, что URL содержит наш поисковый запрос: http://127.0.0.1:8000/search/?q=san+diego
.
Однако результат не изменился! Это связано с тем, что наш SearchResultsView
все еще содержит изначально вписанные значения. Последний шаг — это взять пользовательский поисковый запрос, представленный нашим q
bp URL, и передать его дальше в логику нашего приложения.
1 2 3 4 5 6 7 8 9 10 11 12 |
# cities/views.py ... class SearchResultsView(ListView): model = City template_name = 'search_results.html' def get_queryset(self): # новый query = self.request.GET.get('q') object_list = City.objects.filter( Q(name__icontains=query) | Q(state__icontains=query) ) return object_list |
Мы добавили переменную запроса, которая принимает значение q
из формы поискового запроса. Далее, мы обновляем наш фильтр для названий города и штата. И все! Обновите страницу поисковой выдачи — у нее все еще тот же URL с нашим запросом, и мы получаем ожидаемый результат.
Если вы хотите сравнить свой код с официальным источником, вы можете найти его на GitHub (ссылку мы указали в начале этого урока).
Дальнейшие шаги
Наши основы поиска в Django готовы и изучены! Может, вы хотите добавить кнопку в поисковую форму, которую также можно нажать, как и Enter? Или хотите добавить какую-нибудь валидацию формы?
Помимо фильтрации с использованием AND
и OR
, есть и другие факторы, если вам нужен поиск уровня гугла с релевантностью и прочим. На этом выступлении DjangoCon 2014 показывается, насколько глубока может быть поисковая кроличья нора!
Являюсь администратором нескольких порталов по обучению языков программирования Python, Golang и Kotlin. В составе небольшой команды единомышленников, мы занимаемся популяризацией языков программирования на русскоязычную аудиторию. Большая часть статей была адаптирована нами на русский язык и распространяется бесплатно.
E-mail: vasile.buldumac@ati.utm.md
Образование
Universitatea Tehnică a Moldovei (utm.md)
- 2014 — 2018 Технический Университет Молдовы, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Технический Университет Молдовы, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»