add: все для sitemaps.xml + robots.txt
This commit is contained in:
@@ -15,11 +15,16 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.contrib.sitemaps.views import sitemap
|
||||
from django.urls import include, path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from hypn0_site.sitemaps import sitemaps
|
||||
from . import settings
|
||||
|
||||
urlpatterns = [
|
||||
# Карта сайта sitemap.xml для поисковой индексации
|
||||
path("sitemap.xml", sitemap, {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap"),
|
||||
# Админ-сайт с переименованными приложениями (переопределен в frontend/apps.py)
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
path('', include('hypn0_site.urls')),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import hashlib
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
from hashids import Hashids
|
||||
from hypn0.settings import *
|
||||
from django.utils import timezone
|
||||
@@ -157,6 +158,10 @@ class TbHypn0Item(models.Model):
|
||||
def __str__(self) -> str:
|
||||
return f"{self.s_title} ({self.s_hash_id})"
|
||||
|
||||
def get_absolute_url(self) -> str:
|
||||
"""Возвращает канонический URL детальной страницы картины."""
|
||||
return reverse("hypn0_site:gallery_detail", kwargs={"hash_id": self.s_hash_id})
|
||||
|
||||
@property
|
||||
def card_bg_style(self) -> str:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Карты сайта (Sitemaps) для поисковой индексации HypnoSVG.
|
||||
"""
|
||||
from django.contrib.sitemaps import Sitemap
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import TbHypn0Item
|
||||
|
||||
|
||||
class StaticViewSitemap(Sitemap):
|
||||
"""
|
||||
Карта сайта для ключевых страниц и разделов интерфейса.
|
||||
"""
|
||||
changefreq = "daily"
|
||||
|
||||
def items(self):
|
||||
return [
|
||||
("hypn0_site:index", 1.0, "daily"),
|
||||
("hypn0_site:gallery_archive", 0.9, "daily"),
|
||||
]
|
||||
|
||||
def location(self, item):
|
||||
return reverse(item[0])
|
||||
|
||||
def priority(self, item):
|
||||
return item[1]
|
||||
|
||||
def changefreq(self, item):
|
||||
return item[2]
|
||||
|
||||
|
||||
class GalleryItemSitemap(Sitemap):
|
||||
"""
|
||||
Карта сайта для публичных гипно-картин галереи сообщества.
|
||||
"""
|
||||
changefreq = "weekly"
|
||||
|
||||
def items(self):
|
||||
# Включаем только публичные картины, исключая подозрительные/заблокированные
|
||||
return (
|
||||
TbHypn0Item.objects.filter(is_public=True)
|
||||
.exclude(i_level__in=[TbHypn0Item.Level.SHAMED, TbHypn0Item.Level.SUSPICIOUS])
|
||||
.order_by("-d_created_at")
|
||||
)
|
||||
|
||||
def lastmod(self, obj: TbHypn0Item):
|
||||
return obj.d_created_at
|
||||
|
||||
def priority(self, obj: TbHypn0Item):
|
||||
# Динамический приоритет в зависимости от уровня признания в психо-матрице
|
||||
if obj.i_level == TbHypn0Item.Level.IMMORTAL:
|
||||
return 0.9
|
||||
elif obj.i_level == TbHypn0Item.Level.LEVEL_2:
|
||||
return 0.8
|
||||
elif obj.i_level == TbHypn0Item.Level.LEVEL_1:
|
||||
return 0.7
|
||||
return 0.6
|
||||
|
||||
def location(self, obj: TbHypn0Item):
|
||||
return obj.get_absolute_url()
|
||||
|
||||
|
||||
sitemaps = {
|
||||
"static": StaticViewSitemap,
|
||||
"gallery": GalleryItemSitemap,
|
||||
}
|
||||
@@ -1058,3 +1058,92 @@ class RescoreCommandTests(BaseMediaTestCase):
|
||||
self.assertGreater(self.item_immortal.f_score, self.item_candidate.f_score)
|
||||
# Статус бессмертной картины защищен и не должен измениться
|
||||
self.assertEqual(self.item_immortal.i_level, TbHypn0Item.Level.IMMORTAL)
|
||||
|
||||
|
||||
class SitemapTests(BaseMediaTestCase):
|
||||
"""Тестирование автоматической генерации sitemap.xml."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.vid = "123e4567-e89b-12d3-a456-426614174000"
|
||||
svg_bytes = b'<svg><circle/></svg>'
|
||||
|
||||
# Создаем публичные картины разных уровней
|
||||
self.item_immortal = TbHypn0Item(
|
||||
s_title="Шедевр транса",
|
||||
file_svg=ContentFile(svg_bytes, name="immortal.svg"),
|
||||
i_level=TbHypn0Item.Level.IMMORTAL,
|
||||
is_public=True,
|
||||
)
|
||||
self.item_immortal.save(visitor_uuid_or_fp=self.vid)
|
||||
|
||||
self.item_curated = TbHypn0Item(
|
||||
s_title="Одобренная картина",
|
||||
file_svg=ContentFile(svg_bytes, name="curated.svg"),
|
||||
i_level=TbHypn0Item.Level.LEVEL_2,
|
||||
is_public=True,
|
||||
)
|
||||
self.item_curated.save(visitor_uuid_or_fp=self.vid)
|
||||
|
||||
self.item_fresh = TbHypn0Item(
|
||||
s_title="Свежий шум",
|
||||
file_svg=ContentFile(svg_bytes, name="fresh.svg"),
|
||||
i_level=TbHypn0Item.Level.CANDIDATE,
|
||||
is_public=True,
|
||||
)
|
||||
self.item_fresh.save(visitor_uuid_or_fp=self.vid)
|
||||
|
||||
# Создаем непубличную картину и заблокированную (шейминг)
|
||||
self.item_private = TbHypn0Item(
|
||||
s_title="Приватная картина",
|
||||
file_svg=ContentFile(svg_bytes, name="private.svg"),
|
||||
i_level=TbHypn0Item.Level.CANDIDATE,
|
||||
is_public=False,
|
||||
)
|
||||
self.item_private.save(visitor_uuid_or_fp=self.vid)
|
||||
|
||||
self.item_shamed = TbHypn0Item(
|
||||
s_title="Заблокированная картина",
|
||||
file_svg=ContentFile(svg_bytes, name="shamed.svg"),
|
||||
i_level=TbHypn0Item.Level.SHAMED,
|
||||
is_public=True,
|
||||
)
|
||||
self.item_shamed.save(visitor_uuid_or_fp=self.vid)
|
||||
|
||||
def test_item_get_absolute_url(self):
|
||||
expected_url = f"/gallery/{self.item_immortal.s_hash_id}"
|
||||
self.assertEqual(self.item_immortal.get_absolute_url(), expected_url)
|
||||
|
||||
def test_sitemap_xml_renders_valid_xml_with_static_and_gallery_pages(self):
|
||||
response = self.client.get("/sitemap.xml")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("xml", response["Content-Type"])
|
||||
|
||||
content = response.content.decode("utf-8")
|
||||
self.assertIn("<urlset", content)
|
||||
|
||||
# Проверяем наличие главных статических страниц
|
||||
self.assertIn("<loc>http://testserver/</loc>", content)
|
||||
self.assertIn("<loc>http://testserver/gallery</loc>", content)
|
||||
|
||||
# Проверяем наличие публичных картин
|
||||
self.assertIn(f"<loc>http://testserver/gallery/{self.item_immortal.s_hash_id}</loc>", content)
|
||||
self.assertIn(f"<loc>http://testserver/gallery/{self.item_curated.s_hash_id}</loc>", content)
|
||||
self.assertIn(f"<loc>http://testserver/gallery/{self.item_fresh.s_hash_id}</loc>", content)
|
||||
|
||||
# Проверяем отсутствие приватных и заблокированных картин
|
||||
self.assertNotIn(f"<loc>http://testserver/gallery/{self.item_private.s_hash_id}</loc>", content)
|
||||
self.assertNotIn(f"<loc>http://testserver/gallery/{self.item_shamed.s_hash_id}</loc>", content)
|
||||
|
||||
def test_sitemap_priorities_and_changefreq(self):
|
||||
response = self.client.get("/sitemap.xml")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
# Приоритет главной страницы 1.0, архива 0.9, бессмертной 0.9, кураторской 0.8
|
||||
self.assertIn("<priority>1.0</priority>", content)
|
||||
self.assertIn("<priority>0.9</priority>", content)
|
||||
self.assertIn("<priority>0.8</priority>", content)
|
||||
self.assertIn("<priority>0.6</priority>", content)
|
||||
self.assertIn("<changefreq>daily</changefreq>", content)
|
||||
self.assertIn("<changefreq>weekly</changefreq>", content)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Disallow: /admin/
|
||||
Disallow: /generate
|
||||
Disallow: /publish
|
||||
Disallow: /gallery/*/vote
|
||||
Disallow: /gallery/*/download
|
||||
Disallow: /gallery/random
|
||||
Disallow: /*?*
|
||||
|
||||
Clean-param: seed&floor&sort&page /
|
||||
|
||||
Sitemap: https://hypn0.xyz/sitemap.xml
|
||||
Reference in New Issue
Block a user