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 %} избавляют от хардкода ссылок. Теперь перейдём к шаблонам — тому, что увидит пользователь.

Проверьте себя
1. Что делает конструкция <int:pk> в path()?
AСоздаёт целочисленное поле в базе
BЗахватывает из URL только число и передаёт его во view как pk
CОграничивает количество запросов
DШифрует параметр
2. Почему URL не стоит хардкодить, а лучше ссылаться по имени маршрута?
AТак быстрее работает сервер
BПри изменении структуры URL ссылки не сломаются — имена остаются прежними
CИмена короче
DDjango запрещает хардкод