Работа с DetailView, форматирование и обработка кириллицы в Slug

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

Детальная страница статьи

from django.views.generic import DetailView


class ArticleDetailView(DetailView):
    model = Article
    template_name = "blog/articles_detail.html"
    context_object_name = "article"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["title"] = self.object.title
        return context
path("articles/<str:slug>/", ArticleDetailView.as_view(), name="articles_detail"),

DetailView ищет объект по pk или по slug из URL (атрибуты pk_url_kwarg, slug_url_kwarg, slug_field). Найденный объект — в self.object. Чтобы показывать только опубликованные статьи, переопределите get_queryset(): return Article.custom.all().

Проблема кириллицы в слагах

Заголовки статей русские, а адреса хочется латинские: /articles/kak-ustroen-django/. Стандартная slugify кириллицу просто выбрасывает:

>>> from django.utils.text import slugify
>>> slugify("Как устроен Django")
'django'
>>> slugify("Как устроен Django", allow_unicode=True)
'как-устроен-django'

Нужна транслитерация. Пакеты: pytils (русский язык), unidecode (любые языки), или своя таблица.

pip install unidecode

Утилита в modules/services/utils.py, которая ещё и гарантирует уникальность:

from uuid import uuid4

from django.utils.text import slugify
from unidecode import unidecode


def unique_slugify(instance, text, slug_field="slug"):
    """Транслитерирует текст в слаг; при совпадении добавляет короткий случайный хвост."""
    model = instance.__class__
    base = slugify(unidecode(text))[:200] or uuid4().hex[:8]
    slug = base
    while model.objects.filter(**{slug_field: slug}).exclude(pk=instance.pk).exists():
        slug = f"{base}-{uuid4().hex[:4]}"
    return slug
>>> unique_slugify(Article(), "Как устроен Django")
'kak-ustroen-django'

Автоматическое заполнение в save()

from modules.services.utils import unique_slugify


class Article(models.Model):
    ...

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = unique_slugify(self, self.title)
        super().save(*args, **kwargs)

То же самое добавьте в модель Category. Теперь слаг можно не вводить — он появится при сохранении (в админке уберите prepopulated_fields, чтобы браузерная транслитерация не мешала).

Почему if not self.slug

Слаг генерируется один раз. Если перегенерировать его при каждом изменении заголовка, сломаются старые ссылки на статью.

Форматирование страницы

templates/blog/articles_detail.html:

{% extends "main.html" %}
{% load humanize %}

{% block content %}
  <article class="card border-0">
    {% if article.thumbnail %}
      <img src="{{ article.thumbnail.url }}" class="card-img-top" alt="{{ article.title }}">
    {% endif %}
    <div class="card-body">
      <nav class="small mb-2">
        {% for ancestor in article.category.get_ancestors %}
          <a href="{{ ancestor.get_absolute_url }}">{{ ancestor.title }}</a> /
        {% endfor %}
        <a href="{{ article.category.get_absolute_url }}">{{ article.category.title }}</a>
      </nav>
      <h1 class="card-title">{{ article.title }}</h1>
      <p class="text-muted small">
        {{ article.author.username }} · {{ article.time_create|date:"j E Y, H:i" }}
        {% if article.time_update > article.time_create %}· обновлено {{ article.time_update|naturaltime }}{% endif %}
      </p>
      <div class="card-text">{{ article.full_description|linebreaks }}</div>
    </div>
  </article>
{% endblock %}
  • «Хлебные крошки» строим из get_ancestors() категории — спасибо MPTT.
  • {% load humanize %} (приложение django.contrib.humanize в INSTALLED_APPS) даёт naturaltime («2 часа назад»), intcomma («1 000 000»).
  • date:"j E Y" — «25 октября 2026»: E выводит месяц в родительном падеже.

Итог

  • DetailView находит объект по slug из URL, объект — self.object.
  • slugify выбрасывает кириллицу — транслитерируем через unidecode/pytils.
  • Слаг генерируем в save() один раз и проверяем уникальность.
  • Форматирование: linebreaks, date, humanize, хлебные крошки из дерева категорий.

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

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

Что вернёт стандартная django.utils.text.slugify("Привет мир")?

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

Напишите ArticleDetailView (наследник DetailView): модель Article, шаблон blog/articles_detail.html, имя объекта в шаблоне — article; в get_context_data добавьте title = заголовок статьи (self.object.title).

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

Переопределите save() модели Article: если слаг пуст, заполните его транслитерацией заголовка функцией unique_slugify(self, self.title). Не забудьте вызвать super().save(*args, **kwargs).

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

Как DetailView находит статью по маршруту articles/<str:slug>/?

Задание 5 Несколько вариантов

Какие фильтры помогут отформатировать вывод статьи?

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