Важнейшей частью любого веб-фреймворка является механизм, который отвечает за маршрутизацию. Во Flask для построения карты маршрутов использовались специальные декораторы. В Django для этого используется свой небольшой eDSL. Он описывает urlpatterns — набор образцов, с которыми сопоставляются пути из каждого входящего запроса.
Каждый образец состоит из описания статических и динамических частей пути в виде строки или регулярного выражения:
- Статические части пути в образце просто проверяются на равенство соответствующим участкам пути в запросе
- Динамические участки пути позволяют захватывать значения и передавать во view в качестве аргументов
Как только выясняется, что путь или его начало совпали с образцом, происходит либо вызов view, либо передача оставшейся части пути во вложенный блок urlpatterns. В большинстве больших Django-проектов urlpatterns вложены друг в друга и представляют собой дерево.
В этом уроке подробно разберем статические и динамические маршруты, а также рассмотрим вложенные urlpatterns и обратные маршруты.
Статические маршруты
Мы с вами уже описали один статический маршрут:
urlpatterns = [
path('', views.index),
]
Здесь path
сопоставляет образец ''
с вьюхой views.index
. Образец «пустая строка» соответствует пустому пути — запросам главной страницы сайта. Любой не пустой путь не совпадет с таким образцом. Статические образцы обычно описываются строками вида fruits/apples/golden_one
и ожидают запросов строго по этому же пути.
Имя домена не фигурирует в urlpatterns, что позволяет размещать одно и то же приложение на любом домене.
Динамические маршруты
Авторы Django — сторонники использования читаемых URL. Это означает, что маршруты в Django-приложениях выглядят так, что понятно, куда ведет путь. Например, по пути /users/42/pets/101/med_info/
можно догадаться, что запрашивается медицинская информация (med_info) для питомца с идентификатором 101 (pets/101). Он принадлежит пользователю с идентификатором 42 (user/42).
Иногда получается пойти дальше и вместо идентификаторов использовать имена. Например, такое возможно для имен пользователей, которые обычно уникальны в пределах системы. URL при использовании имен может выглядеть так: /users/~bob/books/
.
Пути, которые включают в себя данные — идентификаторы и имена — называются динамическими. И динамические маршруты используются как раз с такими путями.
В образце, который описывает динамический маршрут, указываются именованные динамические участки. Каждый такой участок обрабатывает свою часть пути и определяет значение для аргумента, который будет передан во view. В итоге от view уже не требуется какая-либо обработка пути, хотя это и возможно.
Опишем urlpatterns для примера пути, который приведен выше:
## urls.py
urlpatterns = [
path('users/<int:user_id>/pets/<int:pet_id>/med_info/', med_info_view),
…
]
## views.py
def med_info_view(request, user_id, pet_id):
...
Здесь <int:XXX>
означает ту самую динамическую часть пути. int
означает, что в этом участке пути ожидается целое число в виде строки. Если сервер получит запрос по пути /users/42/pets/101/med_info/
, маршрутизация закончится вызовом вью med_info_view(request, user_id=42, pet_id=101)
.
Кроме int
Django предоставляет и другие преобразователи путей — path converters. Более того, можно определять и собственные. А если пути специфические, то всегда можно использовать регулярные выражения, чтобы выделить интересные нам части пути.
Вложенные urlpatterns
Иногда маршрутов становится слишком много и среди них намечаются группы, у которых общая статическая часть. Например, это маршруты ко views одного приложения. В этом случае стоит воспользоваться возможностью включения одних urlpatterns в другие.
Предположим, у нас в проекте есть приложение project.users
, в котором все views находятся под общим префиксом /users/. Нам достаточно создать модуль project.users.urls
с описанием urlpatterns уже без префикса и подключить модуль в корневой project.urls
:
# project.users.urls
from django.urls import path
from project.users import views
urlpatterns = [
path('', views.users_view),
path('<int:user_id>/pets/<int:pet_id>/med_info/', views.pet_med_info_view),
…
]
# project.urls
from django.urls import path, include
urlpatterns = [
…
path('users/', include('project.users.urls')),
…
]
В новом наборе urlpatterns у образцов нет префикса users. А в основном urlpatterns указано, что все пути, которые начинаются с users, нужно сопоставлять с образцами из project.users.urls
.
Мы подключили вложенные urlpatterns с помощью функции django.urls.include
и указали модуль в виде строки. Можно импортировать модуль и указать вместо цели маршрута сразу его: path('users/', project.users.urls)
— эти два варианта эквивалентны. Но неявное подключение вместо импорта решает одну важную задачу: избавляет от потенциальных циклических импортов.
Ранее мы закомментировали в нашем мини-проекте строчку path('admin/', admin.site.urls)
. Это тоже включение админки в нашу карту маршрутов по префиксу admin. Подобным образом в приложение часто подключаются сторонние пакеты, у которых собственные маршруты.
Обратные маршруты или reverse
Часто нужно получить для определенного маршрута правильный путь. Например, необходимо кому-то дать ссылку на медицинскую карточку питомца пользователя. Если мы будем вручную собирать путь из строк, то при изменениях в маршруте новый путь может стать некорректным.
Чтобы была возможность для любого маршрута всегда получить правильный путь, нужно произвести операцию, обратную маршрутизации — у Django есть функции reverse и reverse_lazy. Они позволяют получить путь по имени маршрута. Поэтому маршруты, которые нужно обращать, необходимо поименовать (задать уникальное имя):
urlpatterns = [
…
path(
'<int:user_id>/pets/<int:pet_id>/med_info/',
views.pet_med_info_view,
name='pet_med_info', # <--- задаем имя маршруту
),
…
]
Когда маршрут поименован, можно получить путь вызовом вида reverse('pet_med_info', kwargs={'user_id': 42, 'pet_id': 101})
. Как бы ни менялась маршрутизация в дальнейшем, пока путь содержит те же именованные участки и назван по-старому, эта функция будет давать актуальный для маршрута путь.
Функция reverse_lazy
нужна, когда путь нужно получить на этапе инициализации программы, например, при описании class based views. Во время инициализации путь может потребоваться до того, как вся карта маршрутов будет построена. И тут функция reverse_lazy
всего лишь оставляет обещание вернуть путь, когда он реально понадобится — когда сервер уже начнет отвечать на запросы.
В итоге reverse_lazy
используем в атрибутах классов, а в теле вьюх и шаблонах — reverse
. Последняя работает быстрее, но только с готовой картой маршрутов.
Самостоятельная работа
- Сделайте так, чтобы
hexlet_django_blog.article.views.index
принимала строковый параметр "tags" и целочисленный параметр "article_id" из пути/articles/tags/article_id
и выводила текст в видеСтатья номер 42. Тег python
- Назначьте
hexlet_django_blog.article.views.index
имя "article" - Сделайте так, чтобы открытие "домашней страницы" делало перенаправление на
/articles/python/42
. Для этого используйте django.shortcuts.redirect и django.urls.reverse. Не задавайте URL напрямую, используйте обратный маршрут
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.