path, re_path, include, namespace, reverse, get_object_or_404
URLconf — исполняемый модуль Python со списком urlpatterns. Django снимает ведущий /, проверяет маршруты сверху вниз и вызывает первую совпавшую view. Поэтому URL — это не только красивый адрес, но и упорядоченный публичный контракт приложения.
Материал актуален для Django 5.2 LTS и 6.0.
path() и встроенные конвертерыfrom django.urls import path
from . import views
app_name = "articles"
urlpatterns = [
path("", views.article_list, name="list"),
path("new/", views.article_create, name="create"),
path("<slug:slug>/", views.article_detail, name="detail"),
]Маршрут <slug:slug> передаст view именованный аргумент slug. Статический new/ расположен раньше: встроенный slug также принял бы строку new, и порядок иначе сделал бы форму создания недостижимой.
Встроенные конвертеры:
| Конвертер | Что принимает |
|---|---|
str | непустую строку без /; это тип по умолчанию |
int | ноль или положительное целое из ASCII-цифр |
slug | ASCII-буквы, цифры, _ и - |
uuid | UUID в каноническом формате; стандартный маршрут использует строчные hex-буквы |
path | непустую строку, включая / |
path не делает файловый доступ безопасным. Значение всё равно нельзя напрямую соединять с каталогом: нужны нормализация пути, проверка разрешённого корня и авторизация.
re_path()Для обычных ресурсов конвертеры читаются лучше. re_path() нужен, если формат действительно выражается регулярным выражением:
from django.urls import re_path
urlpatterns = [
re_path(
r"^archive/(?P<year>[0-9]{4})(?:/(?P<month>0[1-9]|1[0-2]))?/$",
views.archive,
name="archive",
),
re_path(
r"^download/(?P<name>[^/]+)\.(?P<format>pdf|epub|mobi)$",
views.download,
name="download",
),
]В первом шаблоне месяц необязателен вместе с предшествующим /. Именованные группы становятся keyword-аргументами. Если regex содержит и именованные, и неименованные группы, Django передаёт только именованные — смешивать стили не стоит.
re_path() не является «старым path». Он актуален, просто регулярное выражение сложнее читать, обращать через reverse() и тестировать.
Корневой модуль задаёт крупные префиксы:
# config/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("articles/", include("articles.urls")),
path("accounts/", include("django.contrib.auth.urls")),
]Приложение хранит собственные относительные маршруты и app_name. Получаются имена articles:list, articles:create и articles:detail. Namespace позволяет двум приложениям иметь маршрут detail без конфликта.
Глубоко вложенные списки include([...]) технически возможны, но отдельные urls.py обычно проще навигации и тестирования.
reverse() отделяет вызывающий код от конкретной строки маршрута:
from django.urls import reverse
detail_url = reverse(
"articles:detail",
kwargs={"slug": "django-orm"},
)В шаблоне используется тот же контракт:
<a href="{% url 'articles:detail' slug=article.slug %}">
{{ article.title }}
</a>redirect("articles:detail", slug=article.slug) сначала делает reverse, затем возвращает перенаправление. Жёсткая строка "/articles/" допустима для внешнего адреса или специального случая, но внутренние маршруты лучше именовать.
reverse() принимает query и fragment, поэтому кодирование параметров не нужно писать вручную:
url = reverse(
"articles:list",
query={"page": 2, "tag": ["django", "orm"]},
fragment="results",
)
# /articles/?page=2&tag=django&tag=orm#resultsЭто корректно сохраняет повторяющиеся параметры и экранирует значения. В legacy-коде встречается f-строка с пользовательским вводом в query string; её заменяют на reverse(..., query=...) либо urllib.parse.urlencode(..., doseq=True) для Django до 5.2.
reverse() и reverse_lazy()Обычный reverse() вычисляется немедленно. В атрибуте класса URLconf может быть ещё не загружен, поэтому generic view использует ленивый вариант:
from django.urls import reverse_lazy
from django.views.generic import DeleteView
class ArticleDeleteView(DeleteView):
model = Article
success_url = reverse_lazy("articles:list")Внутри get_success_url() маршрутизация уже готова, и обычный reverse() понятнее.
get_object_or_404() принимает модель, Manager или QuerySet:
from django.shortcuts import get_object_or_404, render
def article_detail(request, slug):
article = get_object_or_404(
Article.objects.published().select_related("author"),
slug=slug,
)
return render(request, "articles/detail.html", {"article": article})Функция преобразует DoesNotExist в Http404, но не проверяет права автоматически. Политика доступа выражена переданным QuerySet. MultipleObjectsReturned не превращается в 404 — уникальность slug должна обеспечиваться схемой базы.
get_list_or_404() возвращает список и выдаёт 404 при пустой выборке. Для обычного каталога пустая страница часто нормальна, поэтому этот shortcut нужен реже.
Конвертер участвует и в распознавании URL, и в reverse():
class MonthConverter:
regex = r"0[1-9]|1[0-2]"
def to_python(self, value):
return int(value)
def to_url(self, value):
month = int(value)
if not 1 <= month <= 12:
raise ValueError("month must be between 1 and 12")
return f"{month:02d}"from django.urls import path, register_converter
from .converters import MonthConverter
register_converter(MonthConverter, "month")
urlpatterns = [
path("archive/<int:year>/<month:month>/", views.archive, name="month"),
]to_python() преобразует фрагмент входящего пути. to_url() преобразует значение при reverse и должен отклонять неподходящее значение через ValueError. Regex и обе функции должны описывать один домен.
resolve() сообщает, какая view соответствует пути, а reverse() проверяет обратное направление:
from django.test import SimpleTestCase
from django.urls import resolve, reverse
class ArticleUrlsTests(SimpleTestCase):
def test_detail_round_trip(self):
url = reverse("articles:detail", kwargs={"slug": "django-orm"})
match = resolve(url)
self.assertEqual(match.view_name, "articles:detail")
self.assertEqual(match.kwargs, {"slug": "django-orm"})Добавьте тесты конфликтных статических слов вроде new, допустимых границ конвертера и завершающего slash. Это дешевле, чем узнавать о затенённом маршруте из production.
Проект должен выбрать последовательный стиль. При CommonMiddleware и APPEND_SLASH=True Django может перенаправить адрес без slash на вариант со slash, если первый не совпал. Для изменяющего POST такое перенаправление не следует считать способом исправления клиента: адрес формы и API должен быть корректным сразу.
Helper static() можно использовать для пользовательских media только в локальном DEBUG-режиме:
from django.conf import settings
from django.conf.urls.static import static
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT,
)Это не production file server. Статику django.contrib.staticfiles обслуживает runserver в разработке; в production файлы собирают collectstatic и отдают web server/CDN/storage по выбранной архитектуре.
Syndication feed также подключается как view-объект, а не через include("django.contrib.syndication.views"):
path("feed/", LatestEntriesFeed(), name="feed")django.conf.urls.url() удалён в Django 4.0. Его замена — path() для конвертеров или re_path() для regex. Сам re_path() не legacy.
Функции django.core.urlresolvers.reverse из старых проектов перенесены в django.urls ещё в Django 2.0. При поддержке legacy ищите также жёстко заданные внутренние адреса и ручную сборку query string: они часто не падают сразу, но ломаются при смене префикса или специальных символах.
Далее: Шаблоны: DTL