Диспетчер URL, часть 2
Имена маршрутов и reverse
Плохая практика — писать адреса руками:
<a href="/blog/posts/{{ post.id }}/">{{ post.title }}</a>
Если завтра адрес изменится на /articles/..., придётся искать и править все шаблоны. Вместо этого используют обратное разрешение URL (reverse): строим адрес по имени маршрута.
В Python-коде — функция reverse():
from django.urls import reverse
reverse("index") # "/"
reverse("post_detail", args=[5]) # "/blog/5/"
reverse("post_detail", kwargs={"pk": 5}) # то же самое
В шаблонах — тег {% url %}:
<a href="{% url 'index' %}">Главная</a>
<a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a>
<a href="{% url 'archive' year=2026 month=3 %}">Март 2026</a>
Пространства имён
Что если и в блоге, и в магазине есть маршрут name="detail"? Чтобы имена не конфликтовали, приложению задают пространство имён:
# blog/urls.py
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post_list"),
path("<int:pk>/", views.post_detail, name="post_detail"),
]
Теперь имя пишется через двоеточие:
reverse("blog:post_detail", args=[5])
<a href="{% url 'blog:post_list' %}">Все посты</a>
Кстати, этот сайт делает именно так: ссылки на уроки строятся как {% url 'courses:lesson_detail' course.slug lesson.slug %}.
get_absolute_url
Удобный приём — научить модель саму возвращать свой адрес:
class Post(models.Model):
...
def get_absolute_url(self):
return reverse("blog:post_detail", args=[self.pk])
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
Этот метод используют и админка (кнопка «Смотреть на сайте»), и redirect(post).
Перенаправления
redirect() из django.shortcuts возвращает ответ 302 (временное) или 301 (постоянное перенаправление). Он понимает:
from django.shortcuts import redirect
redirect("blog:post_list") # имя маршрута
redirect("blog:post_detail", pk=5) # имя + параметры
redirect(post) # объект с get_absolute_url()
redirect("https://djangoproject.com") # абсолютный адрес
redirect("index", permanent=True) # 301
Классический сценарий — после успешной отправки формы перенаправить пользователя (паттерн Post/Redirect/Get), чтобы обновление страницы не отправило форму повторно.
GET-параметры
Query string (?q=django&page=2) не участвует в сопоставлении маршрутов. Значения доступны через request.GET — объект, похожий на словарь:
def search(request):
query = request.GET.get("q", "") # "" если параметра нет
page = int(request.GET.get("page", 1))
tags = request.GET.getlist("tag") # ?tag=a&tag=b → ["a", "b"]
return HttpResponse(f"Ищем «{query}», страница {page}")
Используйте .get() вместо request.GET["q"], иначе при отсутствии параметра получите ошибку.
re_path: регулярные выражения
Если встроенных конвертеров не хватает, есть re_path:
from django.urls import re_path
urlpatterns = [
re_path(r"^archive/(?P<year>[0-9]{4})/$", views.year_archive),
]
(?P<year>...) — именованная группа, её значение придёт во view как строка year. В современных проектах re_path нужен редко.
Дополнительные аргументы
Третьим аргументом path() можно передать словарь — он попадёт во view:
path("feed/", views.post_list, {"format": "rss"}, name="feed")
# post_list(request, format="rss")
Свои обработчики ошибок
В корневом urls.py можно задать собственные view для ошибок:
handler404 = "pages.views.page_not_found"
handler500 = "pages.views.server_error"
Подробно о кастомных страницах ошибок — в последнем модуле курса.
Итог
- Давайте маршрутам имена и стройте ссылки через
reverse()и{% url %}. app_nameзадаёт пространство имён:'blog:post_detail'.redirect()принимает имя маршрута, объект или адрес.- Параметры после
?читаются изrequest.GET.
Практика решено 0 из 6
Зачем маршрутам дают имена (name="post_detail")?
В приложении с app_name = "blog" есть маршрут name="post_detail" с параметром pk.
Напишите в шаблоне ссылку на пост с текстом {{ post.title }}, построив адрес тегом url.
<a href="{% url 'blog:post_detail' pk=post.pk %}">{{ post.title }}</a>Напишите view old_page, которая постоянно (301) перенаправляет на маршрут с именем index.
Используйте redirect из django.shortcuts.
from django.shortcuts import redirect
def old_page(request):
return redirect("index", permanent=True)
Пользователь открыл /search/?q=django&page=2. Как во view получить значение q?
Как называется переменная в urls.py приложения, которая задаёт его пространство имён (namespace)?
Что вернёт reverse("blog:post_detail", args=[7]), если маршрут — path("<int:pk>/", ...) и приложение подключено как path("blog/", include("blog.urls"))?
Войдите, чтобы отмечать пройденные уроки.