add: автоматическое создание slug с помощью pytils и чистка от html-мнемоник.

This commit is contained in:
2026-02-12 19:48:20 +03:00
parent a90bcf89e0
commit 53e7e92248
5 changed files with 78 additions and 4 deletions

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.1 on 2026-02-12 16:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('blog', '0004_post_updated_at'),
]
operations = [
migrations.AlterField(
model_name='post',
name='slug',
field=models.SlugField(blank=True, help_text='Уникальная часть адреса. Оставьте пустым для автогенерации.', max_length=255, unique=True, verbose_name='URL (slug)'),
),
]

View File

@@ -1,6 +1,13 @@
from django.db import models
from django.utils import timezone
from django.urls import reverse
from django.utils.text import slugify
import html
# Попробуем импортировать pytils, если он есть
try:
from pytils.translit import slugify as pytils_slugify
except ImportError:
pytils_slugify = None
class PostType(models.TextChoices):
BLOG = 'B', 'Пост в блог'
@@ -19,7 +26,8 @@ class Post(models.Model):
verbose_name="URL (slug)",
max_length=255,
unique=True,
help_text="Уникальная часть адреса. Используйте латиницу, цифры и дефис. Например: my-new-post"
blank=True, # Разрешаем оставлять пустым в админке (заполнится в save)
help_text="Уникальная часть адреса. Оставьте пустым для автогенерации."
)
post_type = models.CharField(
@@ -44,17 +52,21 @@ class Post(models.Model):
help_text="Дата, которая будет отображаться в блоге. Можно запланировать на будущее."
)
updated_at = models.DateTimeField(
"Дата обновления",
verbose_name="Дата обновления",
auto_now=True,
help_text="Автоматически обновляется при каждом сохранении."
)
content = models.TextField(
verbose_name="Контент",
blank=False,
null=False,
help_text="Основной текст публикации. Поддерживает HTML."
)
excerpt = models.TextField(
verbose_name="Краткое описание (тизер)",
blank=False,
null=False,
help_text="Отображается в списке постов. Если оставить пустым, будет взято начало контента."
)
@@ -102,5 +114,27 @@ class Post(models.Model):
def get_absolute_url(self):
if self.post_type == PostType.PAGE:
# Страницы живут в корневом urls.py без namespace
return reverse('page_detail', kwargs={'slug': self.slug})
return reverse('post_detail', kwargs={'slug': self.slug})
# Посты живут в приложении blog с namespace 'blog'
return reverse('blog:post_detail', kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
# Если слаг не заполнен, генерируем его из заголовка
if not self.slug:
# 1. Декодируем HTML-сущности (  -> " ")
clean_title = html.unescape(self.title)
# 2. Генерируем базовый слаг
if pytils_slugify:
base_slug = pytils_slugify(clean_title)
else:
base_slug = slugify(clean_title)
# 3. Уникализация
self.slug = base_slug
counter = 1
while Post.objects.filter(slug=self.slug).exclude(pk=self.pk).exists():
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs)