Диспетчер URL, часть 2

⏱ 20 мин · 🧩 заданий: 6

Имена маршрутов и 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

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Один вариант ответа

Зачем маршрутам дают имена (name="post_detail")?

Задание 2 Написать код

В приложении с app_name = "blog" есть маршрут name="post_detail" с параметром pk. Напишите в шаблоне ссылку на пост с текстом {{ post.title }}, построив адрес тегом url.

Задание 3 Написать код

Напишите view old_page, которая постоянно (301) перенаправляет на маршрут с именем index. Используйте redirect из django.shortcuts.

Задание 4 Один вариант ответа

Пользователь открыл /search/?q=django&page=2. Как во view получить значение q?

Задание 5 Ввод ответа

Как называется переменная в urls.py приложения, которая задаёт его пространство имён (namespace)?

Задание 6 Один вариант ответа

Что вернёт reverse("blog:post_detail", args=[7]), если маршрут — path("<int:pk>/", ...) и приложение подключено как path("blog/", include("blog.urls"))?

Войдите, чтобы отмечать пройденные уроки.