diff --git a/hypn0/hypn0_site/admin.py b/hypn0/hypn0_site/admin.py
index 9f35f3a..06909df 100644
--- a/hypn0/hypn0_site/admin.py
+++ b/hypn0/hypn0_site/admin.py
@@ -191,18 +191,18 @@ class TbHypn0ItemAdmin(admin.ModelAdmin):
"""
form = TbHypn0ItemAdminForm
list_display = ("id", "s_hash_id", "title_preview", "i_level", "i_likes_count", "i_claims_count",
- "i_views_count", "i_file_size", "f_score", "is_public", "d_created_at", )
+ "i_views_count", "file_size_display", "f_score", "is_public", "d_created_at", )
list_display_links = ("id", "s_hash_id", "title_preview")
list_filter = ("i_level", "is_public", "d_created_at")
search_fields = ("s_hash_id", "s_title", "s_promo_title", "s_promo_url")
- readonly_fields = ("id", "s_hash_id", "i_file_size", "d_created_at", "d_updated_at", "svg_preview",)
+ readonly_fields = ("id", "s_hash_id", "file_size_display", "d_created_at", "d_updated_at", "svg_preview",)
fieldsets = (
("ID & Hash-ID", {
"fields": (("id", "s_hash_id",), ),
"classes": ("collapse", ),
}),
("Основная информация", {
- "fields": ("s_title", ("file_svg", "svg_preview", "i_file_size", "is_public", ), )
+ "fields": ("s_title", ("file_svg", "svg_preview", "file_size_display", "is_public", ), )
}),
("Модерация и Smart Retention", {
"fields": (("i_level", "f_score", ), ),
@@ -246,6 +246,20 @@ class TbHypn0ItemAdmin(admin.ModelAdmin):
)
return "Нет файла"
+ @admin.display(description="Размер файла")
+ def file_size_display(self, obj):
+ if obj and obj.file_svg:
+ try:
+ size_bytes = obj.file_svg.size
+ if size_bytes >= 1024 * 1024:
+ return f"{size_bytes / (1024 * 1024):.2f} МБ"
+ elif size_bytes >= 1024:
+ return f"{size_bytes / 1024:.1f} КБ"
+ return f"{size_bytes} Б"
+ except Exception:
+ return "—"
+ return "—"
+
# ----
# АДМИНКА ЛАЙКОВ/ДИСЛАЙКОВ (все только на просмотр)
diff --git a/hypn0/hypn0_site/migrations/0003_remove_tbhypn0item_i_file_size_alter_tbblogpost_slug_and_more.py b/hypn0/hypn0_site/migrations/0003_remove_tbhypn0item_i_file_size_alter_tbblogpost_slug_and_more.py
new file mode 100644
index 0000000..ede78fe
--- /dev/null
+++ b/hypn0/hypn0_site/migrations/0003_remove_tbhypn0item_i_file_size_alter_tbblogpost_slug_and_more.py
@@ -0,0 +1,47 @@
+# Generated by Django 6.1 on 2026-09-02 20:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('hypn0_site', '0002_alter_tbvote_options_alter_tbblogpost_id_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='tbhypn0item',
+ name='i_file_size',
+ ),
+ migrations.AlterField(
+ model_name='tbblogpost',
+ name='slug',
+ field=models.SlugField(blank=True, default='', max_length=200, unique=True, verbose_name='URL-слаг'),
+ ),
+ migrations.AlterField(
+ model_name='tbhypn0item',
+ name='f_score',
+ field=models.FloatField(db_index=True, default=0.0, help_text='Рейтинг для Smart Retention и определения, что удалять при чистке по лимитам, а что оставлять', verbose_name='Рейтинг'),
+ ),
+ migrations.AlterField(
+ model_name='tbhypn0item',
+ name='i_claims_count',
+ field=models.PositiveIntegerField(default=0, help_text='Число жалоб на гипнотическую SVG-картину. Поле используется для отображения в интерфейсе. Для расчета рейтинга популярности намеренно НЕ ИСПОЛЬЗУЕТСЯ. Рейтинг будет рассчитан на основе таблицы Голоса/Лайка с учетом весов лайков и клаймов.', verbose_name='Жалоб'),
+ ),
+ migrations.AlterField(
+ model_name='tbhypn0item',
+ name='i_level',
+ field=models.IntegerField(choices=[(0, 'Candidate: Шум сознания'), (10, 'Voted: Плеск бессознательного'), (30, 'Moderated: Одобрено Мозговым Слизнем'), (1000, 'Locked: Глубокий транс'), (-20, 'Шейминг (аномальный наплыв жалоб)'), (-10, 'Подозрение на накрутку (спайк лайков)')], default=0, help_text='В какой группе находится SVG-файл генерации', verbose_name='Уровень'),
+ ),
+ migrations.AlterField(
+ model_name='tbhypn0item',
+ name='i_likes_count',
+ field=models.PositiveIntegerField(db_index=True, default=1, help_text='Число лайков гипнотической SVG-картины. Поле используется для отображения в интерфейсе. Для расчета рейтинга популярности намеренно НЕ ИСПОЛЬЗУЕТСЯ. Рейтинг будет рассчитан на основе таблицы Голоса/Лайка с учетом весов лайков и клаймов.', verbose_name='Лайков'),
+ ),
+ migrations.AlterField(
+ model_name='tbhypn0item',
+ name='i_views_count',
+ field=models.PositiveIntegerField(default=1, verbose_name='Просмотров'),
+ ),
+ ]
diff --git a/hypn0/hypn0_site/models.py b/hypn0/hypn0_site/models.py
index c4ea5bf..4de68b4 100644
--- a/hypn0/hypn0_site/models.py
+++ b/hypn0/hypn0_site/models.py
@@ -14,7 +14,6 @@ class TbHypn0Item(models.Model):
• s_hash_id: хэш-сумма шеринга для SVG-генерации (Hashids)
• s_title: Заголовок (до 255 символов, разрешен HTML)
• file_svg: SVG-файл генерации
- • i_file_size: размер файла в байтах
• j_metadata: метаданные файла JSON (параметры алгоритма генерации)
• i_likes_count: количество лайков
• i_views_count: количество просмотров
@@ -62,11 +61,6 @@ class TbHypn0Item(models.Model):
verbose_name="SVG-файл",
help_text="Результирующий SVG-файл генерации",
)
- i_file_size = models.PositiveIntegerField(
- default=0,
- verbose_name="Размер файла",
- help_text="Размер файла в байтах",
- )
# НА ВСЯКИЙ СЛУЧАЙ: Метаданные и снимок параметров для возможного клонирования настроек
j_metadata = models.JSONField(
diff --git a/hypn0/hypn0_site/tests.py b/hypn0/hypn0_site/tests.py
index beb873d..6eb392d 100644
--- a/hypn0/hypn0_site/tests.py
+++ b/hypn0/hypn0_site/tests.py
@@ -413,7 +413,6 @@ class GalleryDetailAndDownloadTests(BaseMediaTestCase):
self.item = TbHypn0Item(
s_title="Астральный Транс Сознания #42",
file_svg=ContentFile(svg_code.encode("utf-8"), name="test_item.svg"),
- i_file_size=len(svg_code),
j_metadata={
"shape": "circle",
"cols": 35,
@@ -486,7 +485,6 @@ class GalleryRandomAndNavigationTests(BaseMediaTestCase):
item = TbHypn0Item(
s_title=f"Гипно Картина #{i}",
file_svg=ContentFile(svg_code.encode("utf-8"), name=f"test_item_{i}.svg"),
- i_file_size=len(svg_code),
j_metadata={"cols": 30 + i},
is_public=True,
)
@@ -564,7 +562,6 @@ class UnconsciousMatrixTests(BaseMediaTestCase):
item = TbHypn0Item(
s_title=f"Тестовый транс #{i}",
file_svg=ContentFile(svg_code.encode("utf-8"), name=f"test_matrix_{i}.svg"),
- i_file_size=len(svg_code),
j_metadata={"cols": 20 + i},
is_public=True,
)
diff --git a/hypn0/hypn0_site/views.py b/hypn0/hypn0_site/views.py
index c60cd32..e3fd7b0 100644
--- a/hypn0/hypn0_site/views.py
+++ b/hypn0/hypn0_site/views.py
@@ -246,7 +246,6 @@ def publish(request: HttpRequest) -> HttpResponse:
item = TbHypn0Item(
s_title=title,
file_svg=svg_file,
- i_file_size=len(svg_bytes),
j_metadata=metadata,
i_level=TbHypn0Item.Level.CANDIDATE,
is_public=True,