mod: галерея (8) Общая галерея с фильтрами стала универсальной для всех "этажей"
This commit is contained in:
@@ -794,35 +794,7 @@ class GalleryFreshFloorTests(BaseMediaTestCase):
|
|||||||
item = TbHypn0Item.objects.first()
|
item = TbHypn0Item.objects.first()
|
||||||
self.assertContains(response, f"gallery-card-{item.s_hash_id}")
|
self.assertContains(response, f"gallery-card-{item.s_hash_id}")
|
||||||
|
|
||||||
def test_gallery_floor_fresh_pagination(self):
|
def test_gallery_floors_curated_and_top_getters(self):
|
||||||
# Создаем 20 картин
|
|
||||||
svg_bytes = b'<svg><circle/></svg>'
|
|
||||||
for i in range(20):
|
|
||||||
item = TbHypn0Item(
|
|
||||||
s_title=f"Кандидат #{i}",
|
|
||||||
file_svg=ContentFile(svg_bytes, name=f"cand_{i}.svg"),
|
|
||||||
i_views_count=i,
|
|
||||||
i_level=TbHypn0Item.Level.CANDIDATE,
|
|
||||||
is_public=True,
|
|
||||||
)
|
|
||||||
item.save(visitor_uuid_or_fp=self.vid)
|
|
||||||
|
|
||||||
# Страница 1 (должно быть 16 штук)
|
|
||||||
url = reverse("hypn0_site:gallery_floor", kwargs={"floor_slug": "fresh"})
|
|
||||||
response_p1 = self.client.get(url)
|
|
||||||
self.assertEqual(response_p1.status_code, 200)
|
|
||||||
self.assertContains(response_p1, "Плеск бессознательного")
|
|
||||||
self.assertContains(response_p1, "FRESH STREAM")
|
|
||||||
self.assertEqual(len(response_p1.context["page_obj"]), 16)
|
|
||||||
self.assertContains(response_p1, "Фаза 1 из 2")
|
|
||||||
|
|
||||||
# Страница 2 (должно быть 4 штуки)
|
|
||||||
response_p2 = self.client.get(url, data={"page": 2})
|
|
||||||
self.assertEqual(response_p2.status_code, 200)
|
|
||||||
self.assertEqual(len(response_p2.context["page_obj"]), 4)
|
|
||||||
self.assertContains(response_p2, "Фаза 2 из 2")
|
|
||||||
|
|
||||||
def test_gallery_floors_curated_and_top(self):
|
|
||||||
from hypn0_site.views import get_floor_curated, get_floor_top
|
from hypn0_site.views import get_floor_curated, get_floor_top
|
||||||
|
|
||||||
svg_bytes = b'<svg><circle/></svg>'
|
svg_bytes = b'<svg><circle/></svg>'
|
||||||
@@ -854,30 +826,12 @@ class GalleryFreshFloorTests(BaseMediaTestCase):
|
|||||||
self.assertEqual(len(top_res), 1)
|
self.assertEqual(len(top_res), 1)
|
||||||
self.assertEqual(top_res[0].pk, item_top.pk)
|
self.assertEqual(top_res[0].pk, item_top.pk)
|
||||||
|
|
||||||
# Проверяем рендеринг страниц этажей
|
|
||||||
url_curated = reverse("hypn0_site:gallery_floor", kwargs={"floor_slug": "curated"})
|
|
||||||
resp_c = self.client.get(url_curated)
|
|
||||||
self.assertEqual(resp_c.status_code, 200)
|
|
||||||
self.assertContains(resp_c, "Одобрено Мозговым Слизнем")
|
|
||||||
self.assertContains(resp_c, "CURATED")
|
|
||||||
|
|
||||||
url_top = reverse("hypn0_site:gallery_floor", kwargs={"floor_slug": "top"})
|
|
||||||
resp_t = self.client.get(url_top)
|
|
||||||
self.assertEqual(resp_t.status_code, 200)
|
|
||||||
self.assertContains(resp_t, "Глубокий транс")
|
|
||||||
self.assertContains(resp_t, "TOP TIER")
|
|
||||||
|
|
||||||
# Проверяем отображение на главной странице
|
# Проверяем отображение на главной странице
|
||||||
resp_idx = self.client.get(reverse("hypn0_site:index"))
|
resp_idx = self.client.get(reverse("hypn0_site:index"))
|
||||||
self.assertEqual(resp_idx.status_code, 200)
|
self.assertEqual(resp_idx.status_code, 200)
|
||||||
self.assertContains(resp_idx, f"gallery-card-{item_curated.s_hash_id}")
|
self.assertContains(resp_idx, f"gallery-card-{item_curated.s_hash_id}")
|
||||||
self.assertContains(resp_idx, f"gallery-card-{item_top.s_hash_id}")
|
self.assertContains(resp_idx, f"gallery-card-{item_top.s_hash_id}")
|
||||||
|
|
||||||
def test_gallery_floor_unknown_404(self):
|
|
||||||
url = reverse("hypn0_site:gallery_floor", kwargs={"floor_slug": "non_existent"})
|
|
||||||
response = self.client.get(url)
|
|
||||||
self.assertEqual(response.status_code, 404)
|
|
||||||
|
|
||||||
|
|
||||||
class CardBgStyleTests(BaseMediaTestCase):
|
class CardBgStyleTests(BaseMediaTestCase):
|
||||||
"""Тестирование вычисления адаптивных стилей подложки карточки."""
|
"""Тестирование вычисления адаптивных стилей подложки карточки."""
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ urlpatterns = [
|
|||||||
path("publish", views.publish, name="publish"),
|
path("publish", views.publish, name="publish"),
|
||||||
path("gallery", views.gallery_archive, name="gallery_archive"),
|
path("gallery", views.gallery_archive, name="gallery_archive"),
|
||||||
path("gallery/random", views.gallery_random, name="gallery_random"),
|
path("gallery/random", views.gallery_random, name="gallery_random"),
|
||||||
path("gallery/random-pool", views.gallery_random, name="gallery_random_pool"),
|
|
||||||
path("gallery/floor/<slug:floor_slug>", views.gallery_floor, name="gallery_floor"),
|
|
||||||
path("gallery/<str:hash_id>", views.gallery_detail, name="gallery_detail"),
|
path("gallery/<str:hash_id>", views.gallery_detail, name="gallery_detail"),
|
||||||
path("gallery/<str:hash_id>/download", views.gallery_download, name="gallery_download"),
|
path("gallery/<str:hash_id>/download", views.gallery_download, name="gallery_download"),
|
||||||
path("gallery/<str:hash_id>/vote", views.gallery_vote, name="gallery_vote"),
|
path("gallery/<str:hash_id>/vote", views.gallery_vote, name="gallery_vote"),
|
||||||
|
|||||||
@@ -141,55 +141,6 @@ def gallery_archive(request: HttpRequest) -> HttpResponse:
|
|||||||
return render(request, "gallery/archive.html", context)
|
return render(request, "gallery/archive.html", context)
|
||||||
|
|
||||||
|
|
||||||
def gallery_floor(request: HttpRequest, floor_slug: str) -> HttpResponse:
|
|
||||||
"""
|
|
||||||
Страница полного просмотра конкретного этажа галереи с пагинацией (по 16 карточек).
|
|
||||||
"""
|
|
||||||
floors_config = {
|
|
||||||
"fresh": {
|
|
||||||
"title": "Плеск бессознательного",
|
|
||||||
"badge": "FRESH STREAM",
|
|
||||||
"badge_color": "amber",
|
|
||||||
"subtitle": "Свежие галлюцинации из инкубатора • Первичная оценка сообщества",
|
|
||||||
"getter": get_floor_fresh,
|
|
||||||
},
|
|
||||||
"curated": {
|
|
||||||
"title": "Одобрено Мозговым Слизнем",
|
|
||||||
"badge": "CURATED",
|
|
||||||
"badge_color": "cyan",
|
|
||||||
"subtitle": "Проверено контролем качества • Высокий психоделический резонанс",
|
|
||||||
"getter": get_floor_curated,
|
|
||||||
},
|
|
||||||
"top": {
|
|
||||||
"title": "Глубокий транс",
|
|
||||||
"badge": "TOP TIER",
|
|
||||||
"badge_color": "emerald",
|
|
||||||
"subtitle": "Золотой фонд гипноза • Абсолютное подчинение воли",
|
|
||||||
"getter": get_floor_top,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if floor_slug not in floors_config:
|
|
||||||
raise Http404("Этаж транса не обнаружен в матрице")
|
|
||||||
|
|
||||||
cfg = floors_config[floor_slug]
|
|
||||||
items_qs = cfg["getter"](limit=None)
|
|
||||||
|
|
||||||
paginator = Paginator(items_qs, 16)
|
|
||||||
page_number = request.GET.get("page", 1)
|
|
||||||
page_obj = paginator.get_page(page_number)
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"floor_slug": floor_slug,
|
|
||||||
"floor_title": cfg["title"],
|
|
||||||
"floor_badge": cfg["badge"],
|
|
||||||
"floor_badge_color": cfg["badge_color"],
|
|
||||||
"floor_subtitle": cfg["subtitle"],
|
|
||||||
"page_obj": page_obj,
|
|
||||||
}
|
|
||||||
return render(request, "gallery/floor.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
@ensure_csrf_cookie
|
@ensure_csrf_cookie
|
||||||
def index(request: HttpRequest | None) -> HttpResponse:
|
def index(request: HttpRequest | None) -> HttpResponse:
|
||||||
fresh_items = get_floor_fresh(limit=8)
|
fresh_items = get_floor_fresh(limit=8)
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
{% extends '_base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block TITLE %}{{ floor_title }} — Галерея транса HypnoSVG{% endblock %}
|
|
||||||
|
|
||||||
{% block CONTENT %}
|
|
||||||
<div class="max-w-6xl mx-auto space-y-8">
|
|
||||||
|
|
||||||
<!-- Хлебные крошки и навигация -->
|
|
||||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
|
||||||
<a href="{% url 'hypn0_site:index' %}"
|
|
||||||
class="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-wider text-stone-500 hover:text-stone-900 dark:text-stone-400 dark:hover:text-stone-100 transition-colors">
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
|
||||||
</svg>
|
|
||||||
<span>К генератору и витрине</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<span class="text-xs font-mono text-stone-500 dark:text-stone-400">
|
|
||||||
Фаза {{ page_obj.number }} из {{ page_obj.paginator.num_pages }} (Всего: {{ page_obj.paginator.count }})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Шапка раздела этажа -->
|
|
||||||
<div class="border-b border-stone-200 dark:border-zinc-800 pb-6 space-y-2">
|
|
||||||
<div class="flex flex-wrap items-center gap-3">
|
|
||||||
<h1 class="text-3xl font-black uppercase tracking-tight text-stone-900 dark:text-stone-100">
|
|
||||||
{{ floor_title }}
|
|
||||||
</h1>
|
|
||||||
<span class="text-xs font-bold font-mono px-2.5 py-1 rounded-full bg-{{ floor_badge_color }}-100 dark:bg-{{ floor_badge_color }}-950/70 text-{{ floor_badge_color }}-700 dark:text-{{ floor_badge_color }}-400 border border-{{ floor_badge_color }}-300 dark:border-{{ floor_badge_color }}-800/50">
|
|
||||||
{{ floor_badge }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm text-stone-500 dark:text-stone-400">
|
|
||||||
{{ floor_subtitle }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Сетка картин (8 штук на страницу) -->
|
|
||||||
<div class="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-4 gap-4 sm:gap-6">
|
|
||||||
{% for item in page_obj %}
|
|
||||||
{% include "block/gallery_card.html" %}
|
|
||||||
{% empty %}
|
|
||||||
<div class="col-span-full py-16 text-center text-stone-400 space-y-3 bg-stone-100/50 dark:bg-zinc-900/50 rounded-2xl border border-stone-200 dark:border-zinc-800">
|
|
||||||
<svg class="w-12 h-12 mx-auto opacity-30 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
|
||||||
</svg>
|
|
||||||
<p class="font-medium text-sm">В этой зоне психо-матрицы пока нет зафиксированных картин.</p>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Пагинация (Предыдущая фаза / Следующая фаза) -->
|
|
||||||
{% if page_obj.has_other_pages %}
|
|
||||||
<div class="flex items-center justify-between pt-6 border-t border-stone-200 dark:border-zinc-800">
|
|
||||||
<div>
|
|
||||||
{% if page_obj.has_previous %}
|
|
||||||
<a href="?page={{ page_obj.previous_page_number }}"
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs font-bold bg-stone-100 hover:bg-stone-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-stone-700 dark:text-stone-300 transition-all hover:scale-105 active:scale-95 shadow-sm border border-stone-200 dark:border-zinc-700/60 cursor-pointer">
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
|
||||||
</svg>
|
|
||||||
<span>Предыдущая фаза</span>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs font-bold text-stone-400 dark:text-stone-600 bg-stone-100/50 dark:bg-zinc-900/50 border border-stone-200/50 dark:border-zinc-800/50 opacity-50 cursor-not-allowed">
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
|
|
||||||
</svg>
|
|
||||||
<span>Предыдущая фаза</span>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Номера страниц -->
|
|
||||||
<div class="hidden sm:flex items-center gap-1.5 font-mono text-xs">
|
|
||||||
{% for p in page_obj.paginator.page_range %}
|
|
||||||
{% if p == page_obj.number %}
|
|
||||||
<span class="w-8 h-8 rounded-lg bg-purple-600 text-white font-bold flex items-center justify-center shadow-md">
|
|
||||||
{{ p }}
|
|
||||||
</span>
|
|
||||||
{% else %}
|
|
||||||
<a href="?page={{ p }}"
|
|
||||||
class="w-8 h-8 rounded-lg bg-stone-100 hover:bg-stone-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-stone-700 dark:text-stone-300 flex items-center justify-center transition-colors">
|
|
||||||
{{ p }}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% if page_obj.has_next %}
|
|
||||||
<a href="?page={{ page_obj.next_page_number }}"
|
|
||||||
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs font-bold bg-purple-600 hover:bg-purple-700 text-white transition-all hover:scale-105 active:scale-95 shadow-md border-0 cursor-pointer">
|
|
||||||
<span>Следующая фаза</span>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl text-xs font-bold text-stone-400 dark:text-stone-600 bg-stone-100/50 dark:bg-zinc-900/50 border border-stone-200/50 dark:border-zinc-800/50 opacity-50 cursor-not-allowed">
|
|
||||||
<span>Следующая фаза</span>
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock CONTENT %}
|
|
||||||
@@ -517,7 +517,7 @@
|
|||||||
<p class="text-xs text-stone-400 dark:text-zinc-500">Свежие галлюцинации из инкубатора • Первичная оценка сообщества</p>
|
<p class="text-xs text-stone-400 dark:text-zinc-500">Свежие галлюцинации из инкубатора • Первичная оценка сообщества</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'hypn0_site:gallery_floor' 'fresh' %}" class="text-xs font-bold text-amber-600 dark:text-amber-400 hover:underline">
|
<a href="{% url 'hypn0_site:gallery_archive' %}?floor=fresh" class="text-xs font-bold text-amber-600 dark:text-amber-400 hover:underline">
|
||||||
Смотреть весь поток →
|
Смотреть весь поток →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -546,7 +546,7 @@
|
|||||||
<p class="text-xs text-stone-400 dark:text-zinc-500">Проверено контролем качества • Высокий психоделический резонанс</p>
|
<p class="text-xs text-stone-400 dark:text-zinc-500">Проверено контролем качества • Высокий психоделический резонанс</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'hypn0_site:gallery_floor' 'curated' %}" class="text-xs font-bold text-cyan-600 dark:text-cyan-400 hover:underline">
|
<a href="{% url 'hypn0_site:gallery_archive' %}?floor=curated" class="text-xs font-bold text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||||
Смотреть все одобренные →
|
Смотреть все одобренные →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -575,7 +575,7 @@
|
|||||||
<p class="text-xs text-stone-400 dark:text-zinc-500">Золотой фонд гипноза • Абсолютное подчинение воли</p>
|
<p class="text-xs text-stone-400 dark:text-zinc-500">Золотой фонд гипноза • Абсолютное подчинение воли</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'hypn0_site:gallery_floor' 'top' %}" class="text-xs font-bold text-emerald-600 dark:text-emerald-400 hover:underline">
|
<a href="{% url 'hypn0_site:gallery_archive' %}?floor=top" class="text-xs font-bold text-emerald-600 dark:text-emerald-400 hover:underline">
|
||||||
Смотреть весь золотой фонд →
|
Смотреть весь золотой фонд →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user