From ebf78c2c48fe5fb86651715891b06120892b386b Mon Sep 17 00:00:00 2001 From: erjemin Date: Sat, 26 Sep 2026 16:29:15 +0300 Subject: [PATCH] =?UTF-8?q?mod:=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20CSS=20=D0=B8=20=D1=81=D0=BF?= =?UTF-8?q?=D1=80=D1=8F=D1=82=D0=B0=D0=BD=D0=B0=20=D1=87=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D1=8C=20tailwind=20"=D0=BA=D0=BB=D0=B0=D1=81=D1=81-=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B1=D0=B0=D1=81"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hypn0/hypn0_site/models.py | 24 +++ hypn0/hypn0_site/tests.py | 14 ++ hypn0/templates/block/gallery_card.html | 84 +++------- hypn0/templates/css/tailwind-custom.css | 203 ++++++++++++++++++++++++ hypn0/templates/gallery/archive.html | 57 ++++--- hypn0/templates/gallery/detail.html | 14 +- public/static/css/tailwind.min.css | 2 +- 7 files changed, 300 insertions(+), 98 deletions(-) diff --git a/hypn0/hypn0_site/models.py b/hypn0/hypn0_site/models.py index b795caa..8827cfa 100644 --- a/hypn0/hypn0_site/models.py +++ b/hypn0/hypn0_site/models.py @@ -1,4 +1,6 @@ import hashlib +from html import unescape +import re from django.core.exceptions import ValidationError from django.db import models, transaction, IntegrityError from django.db.models import F @@ -159,6 +161,17 @@ class TbHypn0Item(models.Model): def __str__(self) -> str: return f"{self.s_title} ({self.s_hash_id})" + @property + def s_title_plain(self) -> str: + """ + Возвращает чистый заголовок картины без HTML-тегов и с декодированными мнемониками (  и др.) + для безопасного использования в HTML-атрибутах (title, aria-label, alt, meta). + """ + if not self.s_title: + return "" + text = re.sub(r"<[^>]+>", "", self.s_title) + return unescape(text).replace("\xa0", " ").strip() + def get_absolute_url(self) -> str: """Возвращает канонический URL детальной страницы картины.""" return reverse("hypn0_site:gallery_detail", kwargs={"hash_id": self.s_hash_id}) @@ -564,6 +577,17 @@ class TbBlogPost(models.Model): def __str__(self) -> str: return self.s_title + @property + def s_title_plain(self) -> str: + """ + Возвращает чистый заголовок статьи без HTML-тегов и с декодированными мнемониками + для безопасного использования в мета-тегах и атрибутах. + """ + if not self.s_title: + return "" + text = re.sub(r"<[^>]+>", "", self.s_title) + return unescape(text).replace("\xa0", " ").strip() + def get_absolute_url(self) -> str: """Возвращает канонический URL статьи блога.""" return f"/blog/{self.slug}" if self.slug else f"/blog/{self.pk}" diff --git a/hypn0/hypn0_site/tests.py b/hypn0/hypn0_site/tests.py index 4004190..eeb5a5f 100644 --- a/hypn0/hypn0_site/tests.py +++ b/hypn0/hypn0_site/tests.py @@ -1505,3 +1505,17 @@ class BlogPostModelAndAdminTests(BaseMediaTestCase): resp500 = error_500(req500) self.assertEqual(resp500.status_code, 500) self.assertIn("Критический перегрев неокортекса", resp500.content.decode("utf-8")) + + def test_s_title_plain_property(self): + """Проверка очистки HTML-тегов и мнемоник в s_title_plain для моделей TbHypn0Item и TbBlogPost.""" + item = TbHypn0Item( + s_title="Шедевр ноосферы «Альфа»", + s_hash_id="testPlain", + ) + self.assertEqual(item.s_title_plain, "Шедевр ноосферы «Альфа»") + + post = TbBlogPost( + s_title="Записки Гипножабы: Раздел&Смысл", + slug="test-plain", + ) + self.assertEqual(post.s_title_plain, "Записки Гипножабы: Раздел&Смысл") diff --git a/hypn0/templates/block/gallery_card.html b/hypn0/templates/block/gallery_card.html index ec58bd3..15d0056 100644 --- a/hypn0/templates/block/gallery_card.html +++ b/hypn0/templates/block/gallery_card.html @@ -1,61 +1,25 @@ -{% load static %} -