Работа с DetailView, форматирование и обработка кириллицы в Slug
Детальная страница статьи
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
Что вернёт стандартная django.utils.text.slugify("Привет мир")?
Напишите ArticleDetailView (наследник DetailView): модель Article, шаблон blog/articles_detail.html,
имя объекта в шаблоне — article; в get_context_data добавьте title = заголовок статьи (self.object.title).
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
Переопределите save() модели Article: если слаг пуст, заполните его транслитерацией заголовка
функцией unique_slugify(self, self.title). Не забудьте вызвать super().save(*args, **kwargs).
class Article(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = unique_slugify(self, self.title)
super().save(*args, **kwargs)
Как DetailView находит статью по маршруту articles/<str:slug>/?
Какие фильтры помогут отформатировать вывод статьи?
Войдите, чтобы отмечать пройденные уроки.