URLconf: маршрутизация с path() и reverse
URLconf — это карта сайта для Django: какой адрес какую view вызывает. Современный синтаксис path() делает её читаемой и типобезопасной.
Суть: список urlpatterns сопоставляет URL-шаблоны с views. path() извлекает параметры из адреса с указанием типа, include() подключает маршруты приложений, name даёт имя для обратного построения ссылок.
Современный путь: path()
Маршруты живут в файле urls.py. Современный Django использует функцию path() (старый url() с регулярками устарел). path() понятнее и поддерживает типы параметров прямо в строке:
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="list"),
path("<int:pk>/", views.post_detail, name="detail"),
path("<slug:slug>/", views.post_by_slug, name="by_slug"),
path("create/", views.create_post, name="create"),
]
Конструкции в угловых скобках — это «конвертеры пути». <int:pk> поймает только число и передаст его во view как pk. Есть str, int, slug, uuid, path. Тип проверяется автоматически — нечисловой pk просто не совпадёт с маршрутом.
include() и модульность
Корневой urls.py проекта не должен знать обо всех маршрутах приложений. Вместо этого он подключает их через include():
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")),
]
Так маршруты блога живут в самом приложении, а проект лишь монтирует их под префикс /blog/. Это делает приложение переносимым.
Именованные маршруты и reverse
Самое важное правило: никогда не хардкодьте URL в коде и шаблонах. Вместо /blog/42/ ссылайтесь на маршрут по имени. Тогда при смене структуры URL ничего не сломается. В Python используют reverse, в шаблонах — тег {% url %}:
from django.urls import reverse
url = reverse("blog:detail", kwargs={"pk": 42})
# вернёт "/blog/42/"
Как это работает под капотом
URL-резолвер хранит шаблоны и умеет работать в обе стороны: разбирать входящий путь (resolve) и собирать путь по имени и параметрам (reverse). Это языко-независимая идея «двустороннего словаря имён». Вот её модель:
# Попробуй сам ▶ — reverse: имя маршрута → URL
patterns = {
"blog:list": "/blog/",
"blog:detail": "/blog/{pk}/",
"blog:by_slug":"/blog/{slug}/",
}
def reverse(name, **params):
template = patterns.get(name)
if template is None:
return f"NoReverseMatch: нет маршрута '{name}'"
try:
return template.format(**params)
except KeyError as e:
return f"Ошибка: не передан параметр {e}"
print(reverse("blog:list"))
print(reverse("blog:detail", pk=42))
print(reverse("blog:by_slug", slug="django-orm"))
print(reverse("blog:detail")) # забыли pk
print(reverse("blog:missing")) # нет маршрута
Django делает это надёжнее (проверяет типы конвертеров), но смысл тот же: имя плюс параметры собираются в реальный URL. Меняете структуру — имена остаются, ссылки не ломаются.
get_absolute_url
Хорошая практика — добавить модели метод get_absolute_url, возвращающий reverse(...) на её страницу. Тогда Django (и админка, и дженерики) знают, как построить ссылку на объект, а вы пишете post.get_absolute_url() вместо ручной сборки.
Передача дополнительных параметров
В path() можно передать словарь дополнительных аргументов третьим параметром — они придут во view как именованные. Это удобно, когда одна view обслуживает несколько маршрутов с разным поведением: например, path("archive/", post_list, {"archived": True}, name="archive") и path("", post_list, name="list") зовут одну функцию, но с разным флагом. Так вы переиспользуете логику без копипасты. А для совсем гибких случаев Django поддерживает вложенные include(): префикс может монтировать целый набор маршрутов другого приложения, и URL-резолвер пройдёт по дереву, пока не найдёт совпадение. Этот древовидный разбор и делает маршрутизацию Django масштабируемой: корневой urls.py остаётся коротким, а каждое приложение владеет своими адресами.
Частые ошибки
- Использовать устаревший url() с регулярками. Современный стандарт —
path(). - Хардкодить URL в шаблонах. Используйте
{% url %}и имена маршрутов. - Забыть слеш в конце пути. Django по умолчанию ждёт завершающий слеш (APPEND_SLASH).
- Не задать app_name и namespace. Имена маршрутов из разных приложений могут конфликтовать.
Best practices
- Всегда давайте маршрутам
nameи используйтеreverse/{% url %}. - Подключайте маршруты приложений через
include()и задавайтеapp_name. - Выбирайте подходящий конвертер (
int,slug) — это валидация бесплатно. - Реализуйте
get_absolute_urlв моделях с публичными страницами.
Итоги
URLconf сопоставляет адреса и views. path() с конвертерами типов — современный и безопасный способ. include() делает приложения модульными, а имена маршрутов с reverse/{% url %} избавляют от хардкода ссылок. Теперь перейдём к шаблонам — тому, что увидит пользователь.