From 53e7e92248a75fe73962393555e6ba989c0abbd4 Mon Sep 17 00:00:00 2001 From: erjemin Date: Thu, 12 Feb 2026 19:48:20 +0300 Subject: [PATCH] =?UTF-8?q?add:=20=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE=D0=B5=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20slug=20=D1=81=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=BE=D1=89=D1=8C=D1=8E=20pytils=20=D0=B8=20=D1=87?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=BA=D0=B0=20=D0=BE=D1=82=20html-=D0=BC?= =?UTF-8?q?=D0=BD=D0=B5=D0=BC=D0=BE=D0=BD=D0=B8=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 10 +++++ .../blog/migrations/0005_alter_post_slug.py | 18 +++++++++ etpgrf_site/blog/models.py | 40 +++++++++++++++++-- poetry.lock | 13 +++++- pyproject.toml | 1 + 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 etpgrf_site/blog/migrations/0005_alter_post_slug.py diff --git a/CHANGELOG.md b/CHANGELOG.md index feef195..591bc1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/). +## [Unreleased] — 2025–02–13 + +### Добавлено +- Поле `updated_at` (_Дата обновления_) в модели, админке, блогах, страницах и `sitemaps.xml` для улучшения SEO, GEO и LLMO. +- README.md с описанием проекта онлайн-типографа, его особенностей, технического стека и инструкциями по установке и запуску. + +### Исправлено +- Исправлены ошибки в шаблоне 'post_list.html' (и дизайн в целом). +- Формирование `slag` из `title` при сохранении поста или страницы с использованием библиотеки `pytils` для транслитерации с очистикой от HTML-мнемоник и создания URL-дружественных строк. + ## [0.2.4] - 2025-02-12 ### Добавлено diff --git a/etpgrf_site/blog/migrations/0005_alter_post_slug.py b/etpgrf_site/blog/migrations/0005_alter_post_slug.py new file mode 100644 index 0000000..861d095 --- /dev/null +++ b/etpgrf_site/blog/migrations/0005_alter_post_slug.py @@ -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)'), + ), + ] diff --git a/etpgrf_site/blog/models.py b/etpgrf_site/blog/models.py index 6733beb..d3340f0 100644 --- a/etpgrf_site/blog/models.py +++ b/etpgrf_site/blog/models.py @@ -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) diff --git a/poetry.lock b/poetry.lock index d6a8f1f..40f3a32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -381,6 +381,17 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pytils" +version = "0.4.4" +description = "Russian-specific string utils" +optional = false +python-versions = "*" +files = [ + {file = "pytils-0.4.4-py3-none-any.whl", hash = "sha256:e54c16465a5fdb65d414e2da8045e6cc6de79889acda6143dcef2e1e86a1a840"}, + {file = "pytils-0.4.4.tar.gz", hash = "sha256:9992a96caad57daa211584df1da4fd825f11e836d3ed93011785f1d02ab6f0ca"}, +] + [[package]] name = "regex" version = "2026.1.15" @@ -572,4 +583,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.13" -content-hash = "9610a92fa47d1bd0849512ae842b0fdd68dc06d9917ab676cf5d8f6521700837" +content-hash = "ce33b38ff06b069d35d46c795c2a5f81c0907f288bb662a001ab740760cc90b2" diff --git a/pyproject.toml b/pyproject.toml index 119bff6..006c19b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ etpgrf = "0.1.4" # regex = "^2023.12" # etpgrf подтянет как зависимость # beautifulsoup4 = "^4.10.0" # etpgrf подтянет как зависимость pillow = "^12.1.0" +pytils = "^0.4.4" [build-system] requires = ["poetry-core"]