Работа с URL

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

Зачем красивые адреса

Адрес /blog/17/ ничего не говорит ни человеку, ни поисковой системе. Сравните с /blog/2026/10/25/kak-ustroen-django/:

  • видно, о чём статья и когда опубликована;
  • поисковики учитывают слова в адресе;
  • адресом удобно делиться.

Сделаем адрес поста из даты публикации и слага.

Уникальность слага

Два поста с одинаковым слагом в один день сделают адрес неоднозначным. Параметр unique_for_date запрещает такие дубликаты:

class Post(models.Model):
    ...
    slug = models.SlugField(max_length=250, unique_for_date="publish")

Это проверка уровня валидации модели (админка, ModelForm), а не ограничение базы. Миграция всё равно нужна, чтобы зафиксировать изменение поля:

python manage.py makemigrations blog
python manage.py migrate

Новый маршрут

blog/urls.py:

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path(
        "<int:year>/<int:month>/<int:day>/<slug:post>/",
        views.post_detail,
        name="post_detail",
    ),
]

Конвертеры int и slug проверят формат: /blog/2026/ten/25/.../ не совпадёт с маршрутом.

Новая view

def post_detail(request, year, month, day, post):
    post = get_object_or_404(
        Post,
        status=Post.Status.PUBLISHED,
        slug=post,
        publish__year=year,
        publish__month=month,
        publish__day=day,
    )
    return render(request, "blog/post/detail.html", {"post": post})

Параметр называется post, а не slug, — чтобы совпасть с именем в маршруте. После поиска мы переиспользуем имя для объекта — так часто делают, но если путаетесь, назовите параметр slug, изменив и маршрут.

Часовые пояса

При USE_TZ = True lookups __year/__month/__day работают в текущем часовом поясе (TIME_ZONE). Поэтому пост, опубликованный в 01:00 по Москве, будет иметь в адресе «московскую» дату, хотя в базе хранится время UTC.

Канонический адрес

Главное изменение — в одном месте:

def get_absolute_url(self):
    return reverse(
        "blog:post_detail",
        args=[self.publish.year, self.publish.month, self.publish.day, self.slug],
    )

Все шаблоны используют {{ post.get_absolute_url }}, поэтому ссылки на сайте обновились автоматически. Вот она — польза от get_absolute_url и DRY.

Проверка

Откройте /blog/, кликните по посту — адрес теперь вида /blog/2026/10/25/moj-post/. Старый адрес /blog/5/ больше не работает. Если сайт уже был проиндексирован, стоит оставить старый маршрут с постоянным редиректом на новый:

def post_by_id(request, id):
    post = get_object_or_404(Post.published, id=id)
    return redirect(post, permanent=True)

Итог

  • Адрес поста: год/месяц/день/слаг.
  • unique_for_date="publish" не даёт создать два поста с одинаковым слагом в один день.
  • View ищет по слагу и lookups publish__year/month/day.
  • Благодаря get_absolute_url ссылки во всех шаблонах обновились сами.

Практика решено 0 из 5

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

Напишите маршрут для адресов вида /blog/2026/10/25/moj-post/: параметры year, month, day (целые) и post (слаг), view — views.post_detail, имя — post_detail.

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

Перепишите get_absolute_url, чтобы он строил новый адрес: год, месяц, день из self.publish и self.slug.

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

Обновите get_object_or_404 в post_detail(request, year, month, day, post): ищите опубликованный пост по слагу post и году, месяцу, дню даты publish.

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

Сделайте поле slug уникальным в пределах даты публикации, используя параметр поля unique_for_date.

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

Где проверяется unique_for_date?

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