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'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
|
||||||
|
from hypn0_site.sitemaps import sitemaps
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
# Карта сайта sitemap.xml для поисковой индексации
|
||||||
|
path("sitemap.xml", sitemap, {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap"),
|
||||||
# Админ-сайт с переименованными приложениями (переопределен в frontend/apps.py)
|
# Админ-сайт с переименованными приложениями (переопределен в frontend/apps.py)
|
||||||
path(settings.ADMIN_URL, admin.site.urls),
|
path(settings.ADMIN_URL, admin.site.urls),
|
||||||
path('', include('hypn0_site.urls')),
|
path('', include('hypn0_site.urls')),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
from django.db import models, transaction, IntegrityError
|
from django.db import models, transaction, IntegrityError
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
|
from django.urls import reverse
|
||||||
from hashids import Hashids
|
from hashids import Hashids
|
||||||
from hypn0.settings import *
|
from hypn0.settings import *
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@@ -157,6 +158,10 @@ class TbHypn0Item(models.Model):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.s_title} ({self.s_hash_id})"
|
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
|
@property
|
||||||
def card_bg_style(self) -> str:
|
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.assertGreater(self.item_immortal.f_score, self.item_candidate.f_score)
|
||||||
# Статус бессмертной картины защищен и не должен измениться
|
# Статус бессмертной картины защищен и не должен измениться
|
||||||
self.assertEqual(self.item_immortal.i_level, TbHypn0Item.Level.IMMORTAL)
|
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