Важнейшей частью любого Web-фреймворка является механизм, отвечающий за маршрутизацию. Во Flask для построения карты маршрутов использовались специальные декораторы. В Django для этого используется свой небольшой eDSL, описывающий urlpatterns — набор образцов, с которыми сопоставляются пути из каждого входящего запроса.
Каждый образец состоит из описания статических и динамических частей пути в виде строки или регулярного выражения. Статические части пути в образце просто проверяются на равенство соответствующим участкам пути в запросе. Динамические же участки пути позволяют захватывать (capture) значения и передавать во view в качестве аргументов.
Как только выясняется, что путь или его начало совпали с образцом, происходит либо вызов view, либо передача оставшейся части пути во вложенный блок urlpatterns. В большинстве больших Django-проектов 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 уже не требуется какая-либо обработка пути (хотя это и возможно).
Давайте опишем urlpattern для примера пути, приведённого выше:
urlpatterns = [
path('users/<int:user_id>/pets/<int:pet_id>/med_info', med_info_view),
…
]
Здесь <int:XXX>
означает ту самую динамическую часть пути. Причём "int" здесь означает, что в этом участке пути ожидается целое число (в виде строки). Теперь если сервер получит запрос по пути "/users/42/pets/101/med_info/", маршрутизация закончится вызовом med_info_view(request, user_id=42, pet_id=101)
. Наглядно и удобно!
Кроме "int" Django предоставляет и другие "преобразователи путей" (path converters). Более того, вы можете определять и свои собственные. А если ваши пути совсем уж специфические, то вы всегда можете использовать регулярные выражения для выделения интересных вам частей пути.
Когда маршрутов становится слишком много и среди них намечаются группы, имеющие общую статическую часть — как правило, это маршруты ко views одного приложения — стоит воспользоваться возможностью включения (inclusion) одних 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
(она работает быстрее, но только с готовой картой маршрутов).
hello_django.calc.views.index
принимала целочисленные параметры "a" и "b" из пути /calc/A/B
и выводила текст 40 + 2 = 42
(с указанными числами в качестве значений, само собой!).hello_django.calc.views.index
имя "calc"./calc/40/2
. Для этого используйте django.shortcuts.redirect и django.urls.reverse. Не задавайте URL напрямую, используйте именно обратный маршрут!Вам ответят команда поддержки Хекслета или другие студенты.
Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.
Загляните в раздел «Обсуждение»:
Статья «Ловушки обучения»
Вебинар «Как самостоятельно учиться»
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.
Наши выпускники работают в компаниях:
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.
Зарегистрируйтесь или войдите в свой аккаунт