This commit is contained in:
parent
f04d88e8fd
commit
02ea6314c3
3 changed files with 472 additions and 128 deletions
|
|
@ -13,6 +13,36 @@ if TYPE_CHECKING:
|
|||
|
||||
urlpatterns: list[URLPattern | URLResolver] = [
|
||||
path(route="sitemap.xml", view=core_views.sitemap_view, name="sitemap"),
|
||||
path(
|
||||
route="sitemap-static.xml",
|
||||
view=core_views.sitemap_static_view,
|
||||
name="sitemap-static",
|
||||
),
|
||||
path(
|
||||
route="sitemap-twitch-channels.xml",
|
||||
view=core_views.sitemap_twitch_channels_view,
|
||||
name="sitemap-twitch-channels",
|
||||
),
|
||||
path(
|
||||
route="sitemap-twitch-drops.xml",
|
||||
view=core_views.sitemap_twitch_drops_view,
|
||||
name="sitemap-twitch-drops",
|
||||
),
|
||||
path(
|
||||
route="sitemap-twitch-others.xml",
|
||||
view=core_views.sitemap_twitch_others_view,
|
||||
name="sitemap-twitch-others",
|
||||
),
|
||||
path(
|
||||
route="sitemap-kick.xml",
|
||||
view=core_views.sitemap_kick_view,
|
||||
name="sitemap-kick",
|
||||
),
|
||||
path(
|
||||
route="sitemap-youtube.xml",
|
||||
view=core_views.sitemap_youtube_view,
|
||||
name="sitemap-youtube",
|
||||
),
|
||||
# Core app
|
||||
path(route="", view=include("core.urls", namespace="core")),
|
||||
# Twitch app
|
||||
|
|
|
|||
427
core/views.py
427
core/views.py
|
|
@ -11,6 +11,7 @@ from django.db import connection
|
|||
from django.db.models import Count
|
||||
from django.db.models import Exists
|
||||
from django.db.models import F
|
||||
from django.db.models import Max
|
||||
from django.db.models import OuterRef
|
||||
from django.db.models import Prefetch
|
||||
from django.db.models import Q
|
||||
|
|
@ -119,143 +120,357 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
|||
return context
|
||||
|
||||
|
||||
def _render_urlset_xml(
|
||||
url_entries: list[dict[str, str | dict[str, str]]],
|
||||
) -> str:
|
||||
"""Render a <urlset> sitemap XML string from URL entries.
|
||||
|
||||
Args:
|
||||
url_entries: List of dictionaries containing URL entry data.
|
||||
|
||||
Returns:
|
||||
A string containing the rendered XML.
|
||||
|
||||
"""
|
||||
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
for url_entry in url_entries:
|
||||
xml += " <url>\n"
|
||||
xml += f" <loc>{url_entry['url']}</loc>\n"
|
||||
if url_entry.get("lastmod"):
|
||||
xml += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
|
||||
xml += " </url>\n"
|
||||
xml += "</urlset>"
|
||||
return xml
|
||||
|
||||
|
||||
def _render_sitemap_index_xml(sitemap_entries: list[dict[str, str]]) -> str:
|
||||
"""Render a <sitemapindex> XML string listing sitemap URLs.
|
||||
|
||||
Args:
|
||||
sitemap_entries: List of dictionaries with "loc" and optional "lastmod".
|
||||
|
||||
Returns:
|
||||
A string containing the rendered XML.
|
||||
"""
|
||||
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
xml += '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
for entry in sitemap_entries:
|
||||
xml += " <sitemap>\n"
|
||||
xml += f" <loc>{entry['loc']}</loc>\n"
|
||||
if entry.get("lastmod"):
|
||||
xml += f" <lastmod>{entry['lastmod']}</lastmod>\n"
|
||||
xml += " </sitemap>\n"
|
||||
xml += "</sitemapindex>"
|
||||
return xml
|
||||
|
||||
|
||||
def _build_base_url(request: HttpRequest) -> str:
|
||||
"""Return base url including scheme and host."""
|
||||
return f"{request.scheme}://{request.get_host()}"
|
||||
|
||||
|
||||
# MARK: /sitemap.xml
|
||||
def sitemap_view(request: HttpRequest) -> HttpResponse: # noqa: PLR0915
|
||||
"""Generate a dynamic XML sitemap for search engines.
|
||||
def sitemap_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap index pointing to per-section sitemap files.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: XML sitemap.
|
||||
HttpResponse: The rendered sitemap index XML.
|
||||
"""
|
||||
base_url: str = f"{request.scheme}://{request.get_host()}"
|
||||
base_url: str = _build_base_url(request)
|
||||
|
||||
# Start building sitemap XML
|
||||
# Compute last modified per-section so search engines can more intelligently crawl.
|
||||
# Do not fabricate a lastmod date if the section has no data.
|
||||
twitch_channels_lastmod = Channel.objects.aggregate(max=Max("updated_at"))["max"]
|
||||
twitch_drops_lastmod = max(
|
||||
[
|
||||
dt
|
||||
for dt in [
|
||||
DropCampaign.objects.aggregate(max=Max("updated_at"))["max"],
|
||||
RewardCampaign.objects.aggregate(max=Max("updated_at"))["max"],
|
||||
]
|
||||
if dt is not None
|
||||
]
|
||||
or [None],
|
||||
)
|
||||
twitch_others_lastmod = max(
|
||||
[
|
||||
dt
|
||||
for dt in [
|
||||
Game.objects.aggregate(max=Max("updated_at"))["max"],
|
||||
Organization.objects.aggregate(max=Max("updated_at"))["max"],
|
||||
ChatBadgeSet.objects.aggregate(max=Max("updated_at"))["max"],
|
||||
]
|
||||
if dt is not None
|
||||
]
|
||||
or [None],
|
||||
)
|
||||
kick_lastmod = KickDropCampaign.objects.aggregate(max=Max("updated_at"))["max"]
|
||||
|
||||
sitemap_entries: list[dict[str, str]] = [
|
||||
{"loc": f"{base_url}/sitemap-static.xml"},
|
||||
{"loc": f"{base_url}/sitemap-twitch-channels.xml"},
|
||||
{"loc": f"{base_url}/sitemap-twitch-drops.xml"},
|
||||
{"loc": f"{base_url}/sitemap-twitch-others.xml"},
|
||||
{"loc": f"{base_url}/sitemap-kick.xml"},
|
||||
{"loc": f"{base_url}/sitemap-youtube.xml"},
|
||||
]
|
||||
|
||||
if twitch_channels_lastmod is not None:
|
||||
sitemap_entries[1]["lastmod"] = twitch_channels_lastmod.isoformat()
|
||||
if twitch_drops_lastmod is not None:
|
||||
sitemap_entries[2]["lastmod"] = twitch_drops_lastmod.isoformat()
|
||||
if twitch_others_lastmod is not None:
|
||||
sitemap_entries[3]["lastmod"] = twitch_others_lastmod.isoformat()
|
||||
if kick_lastmod is not None:
|
||||
sitemap_entries[4]["lastmod"] = kick_lastmod.isoformat()
|
||||
|
||||
xml_content: str = _render_sitemap_index_xml(sitemap_entries)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
# MARK: /sitemap-static.xml
|
||||
def sitemap_static_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing the main static pages.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
|
||||
# Include the canonical top-level pages, the main app dashboards, and key list views.
|
||||
# Using reverse() keeps these URLs correct if route patterns change.
|
||||
static_route_names: list[str] = [
|
||||
"core:dashboard",
|
||||
# "core:search",
|
||||
"core:dataset_backups",
|
||||
"core:docs_rss",
|
||||
# Core RSS/Atom feeds
|
||||
# "core:campaign_feed",
|
||||
# "core:game_feed",
|
||||
# "core:organization_feed",
|
||||
# "core:reward_campaign_feed",
|
||||
# "core:campaign_feed_atom",
|
||||
# "core:game_feed_atom",
|
||||
# "core:organization_feed_atom",
|
||||
# "core:reward_campaign_feed_atom",
|
||||
# "core:campaign_feed_discord",
|
||||
# "core:game_feed_discord",
|
||||
# "core:organization_feed_discord",
|
||||
# "core:reward_campaign_feed_discord",
|
||||
# Twitch pages
|
||||
"twitch:dashboard",
|
||||
"twitch:badge_list",
|
||||
"twitch:campaign_list",
|
||||
"twitch:channel_list",
|
||||
"twitch:emote_gallery",
|
||||
"twitch:games_grid",
|
||||
"twitch:games_list",
|
||||
"twitch:org_list",
|
||||
"twitch:reward_campaign_list",
|
||||
# Kick pages
|
||||
"kick:dashboard",
|
||||
"kick:campaign_list",
|
||||
"kick:game_list",
|
||||
"kick:category_list",
|
||||
"kick:organization_list",
|
||||
# Kick RSS/Atom feeds
|
||||
# "kick:campaign_feed",
|
||||
# "kick:game_feed",
|
||||
# "kick:category_feed",
|
||||
# "kick:organization_feed",
|
||||
# "kick:campaign_feed_atom",
|
||||
# "kick:game_feed_atom",
|
||||
# "kick:category_feed_atom",
|
||||
# "kick:organization_feed_atom",
|
||||
# "kick:campaign_feed_discord",
|
||||
# "kick:game_feed_discord",
|
||||
# "kick:category_feed_discord",
|
||||
# "kick:organization_feed_discord",
|
||||
# YouTube
|
||||
"youtube:index",
|
||||
]
|
||||
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = []
|
||||
for route_name in static_route_names:
|
||||
url = reverse(route_name)
|
||||
sitemap_urls.append({"url": f"{base_url}{url}"})
|
||||
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
# MARK: /sitemap-twitch-channels.xml
|
||||
def sitemap_twitch_channels_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing Twitch channel pages.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = []
|
||||
|
||||
# Static pages
|
||||
sitemap_urls.extend([
|
||||
{"url": f"{base_url}/", "priority": "1.0", "changefreq": "daily"},
|
||||
{"url": f"{base_url}/campaigns/", "priority": "0.9", "changefreq": "daily"},
|
||||
{
|
||||
"url": f"{base_url}/reward-campaigns/",
|
||||
"priority": "0.9",
|
||||
"changefreq": "daily",
|
||||
},
|
||||
{"url": f"{base_url}/games/", "priority": "0.9", "changefreq": "weekly"},
|
||||
{
|
||||
"url": f"{base_url}/organizations/",
|
||||
"priority": "0.8",
|
||||
"changefreq": "weekly",
|
||||
},
|
||||
{"url": f"{base_url}/channels/", "priority": "0.8", "changefreq": "weekly"},
|
||||
{"url": f"{base_url}/badges/", "priority": "0.7", "changefreq": "monthly"},
|
||||
{"url": f"{base_url}/emotes/", "priority": "0.7", "changefreq": "monthly"},
|
||||
{"url": f"{base_url}/search/", "priority": "0.6", "changefreq": "monthly"},
|
||||
])
|
||||
|
||||
# Dynamic detail pages - Games
|
||||
games: QuerySet[Game] = Game.objects.all()
|
||||
for game in games:
|
||||
entry: dict[str, str | dict[str, str]] = {
|
||||
"url": f"{base_url}{reverse('twitch:game_detail', args=[game.twitch_id])}",
|
||||
"priority": "0.8",
|
||||
"changefreq": "weekly",
|
||||
}
|
||||
if game.updated_at:
|
||||
entry["lastmod"] = game.updated_at.isoformat()
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Dynamic detail pages - Campaigns
|
||||
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.all()
|
||||
for campaign in campaigns:
|
||||
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {
|
||||
"url": full_url,
|
||||
"priority": "0.7",
|
||||
"changefreq": "weekly",
|
||||
}
|
||||
if campaign.updated_at:
|
||||
entry["lastmod"] = campaign.updated_at.isoformat()
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Dynamic detail pages - Organizations
|
||||
orgs: QuerySet[Organization] = Organization.objects.all()
|
||||
for org in orgs:
|
||||
resource_url = reverse("twitch:organization_detail", args=[org.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {
|
||||
"url": full_url,
|
||||
"priority": "0.7",
|
||||
"changefreq": "weekly",
|
||||
}
|
||||
if org.updated_at:
|
||||
entry["lastmod"] = org.updated_at.isoformat()
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Dynamic detail pages - Channels
|
||||
channels: QuerySet[Channel] = Channel.objects.all()
|
||||
for channel in channels:
|
||||
resource_url = reverse("twitch:channel_detail", args=[channel.twitch_id])
|
||||
resource_url: str = reverse("twitch:channel_detail", args=[channel.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {
|
||||
"url": full_url,
|
||||
"priority": "0.6",
|
||||
"changefreq": "weekly",
|
||||
}
|
||||
entry: dict[str, str | dict[str, str]] = {"url": full_url}
|
||||
if channel.updated_at:
|
||||
entry["lastmod"] = channel.updated_at.isoformat()
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Dynamic detail pages - Badges
|
||||
badge_sets: QuerySet[ChatBadgeSet] = ChatBadgeSet.objects.all()
|
||||
for badge_set in badge_sets:
|
||||
resource_url = reverse("twitch:badge_set_detail", args=[badge_set.set_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
sitemap_urls.append({
|
||||
"url": full_url,
|
||||
"priority": "0.5",
|
||||
"changefreq": "monthly",
|
||||
})
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
# MARK: /sitemap-twitch-drops.xml
|
||||
def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing Twitch drop campaign pages.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = []
|
||||
|
||||
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.all()
|
||||
for campaign in campaigns:
|
||||
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str] = {"url": full_url}
|
||||
if campaign.updated_at:
|
||||
entry["lastmod"] = campaign.updated_at.isoformat()
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Dynamic detail pages - Reward Campaigns
|
||||
reward_campaigns: QuerySet[RewardCampaign] = RewardCampaign.objects.all()
|
||||
for reward_campaign in reward_campaigns:
|
||||
resource_url = reverse(
|
||||
"twitch:reward_campaign_detail",
|
||||
args=[
|
||||
reward_campaign.twitch_id,
|
||||
],
|
||||
args=[reward_campaign.twitch_id],
|
||||
)
|
||||
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {
|
||||
"url": full_url,
|
||||
"priority": "0.6",
|
||||
"changefreq": "weekly",
|
||||
}
|
||||
entry: dict[str, str | dict[str, str]] = {"url": full_url}
|
||||
if reward_campaign.updated_at:
|
||||
entry["lastmod"] = reward_campaign.updated_at.isoformat()
|
||||
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Build XML
|
||||
xml_content = '<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
xml_content += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
for url_entry in sitemap_urls:
|
||||
xml_content += " <url>\n"
|
||||
xml_content += f" <loc>{url_entry['url']}</loc>\n"
|
||||
if url_entry.get("lastmod"):
|
||||
xml_content += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
|
||||
xml_content += (
|
||||
f" <changefreq>{url_entry.get('changefreq', 'monthly')}</changefreq>\n"
|
||||
)
|
||||
xml_content += f" <priority>{url_entry.get('priority', '0.5')}</priority>\n"
|
||||
xml_content += " </url>\n"
|
||||
|
||||
xml_content += "</urlset>"
|
||||
# MARK: /sitemap-twitch-others.xml
|
||||
def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing other Twitch pages (games, organizations, badges, emotes).
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = []
|
||||
|
||||
games: QuerySet[Game] = Game.objects.all()
|
||||
for game in games:
|
||||
resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {"url": full_url}
|
||||
|
||||
if game.updated_at:
|
||||
entry["lastmod"] = game.updated_at.isoformat()
|
||||
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
orgs: QuerySet[Organization] = Organization.objects.all()
|
||||
for org in orgs:
|
||||
resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {"url": full_url}
|
||||
|
||||
if org.updated_at:
|
||||
entry["lastmod"] = org.updated_at.isoformat()
|
||||
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
badge_sets: QuerySet[ChatBadgeSet] = ChatBadgeSet.objects.all()
|
||||
for badge_set in badge_sets:
|
||||
resource_url = reverse("twitch:badge_set_detail", args=[badge_set.set_id])
|
||||
full_url = f"{base_url}{resource_url}"
|
||||
sitemap_urls.append({"url": full_url})
|
||||
|
||||
# Emotes currently don't have individual detail pages, but keep a listing here.
|
||||
sitemap_urls.append({"url": f"{base_url}/emotes/"})
|
||||
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
# MARK: /sitemap-kick.xml
|
||||
def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing Kick drops and related pages.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = []
|
||||
|
||||
kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.all()
|
||||
for campaign in kick_campaigns:
|
||||
resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id])
|
||||
full_url: str = f"{base_url}{resource_url}"
|
||||
entry: dict[str, str | dict[str, str]] = {"url": full_url}
|
||||
|
||||
if campaign.updated_at:
|
||||
entry["lastmod"] = campaign.updated_at.isoformat()
|
||||
|
||||
sitemap_urls.append(entry)
|
||||
|
||||
# Include Kick organizations and game list pages
|
||||
sitemap_urls.extend((
|
||||
{"url": f"{base_url}/kick/organizations/"},
|
||||
{"url": f"{base_url}/kick/games/"},
|
||||
))
|
||||
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
# MARK: /sitemap-youtube.xml
|
||||
def sitemap_youtube_view(request: HttpRequest) -> HttpResponse:
|
||||
"""Sitemap containing the YouTube page(s).
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The rendered sitemap XML.
|
||||
"""
|
||||
base_url: str = _build_base_url(request)
|
||||
|
||||
sitemap_urls: list[dict[str, str | dict[str, str]]] = [
|
||||
{"url": f"{base_url}{reverse('youtube:index')}"},
|
||||
]
|
||||
|
||||
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||
return HttpResponse(xml_content, content_type="application/xml")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ from django.test import RequestFactory
|
|||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from kick.models import KickCategory
|
||||
from kick.models import KickDropCampaign
|
||||
from kick.models import KickOrganization
|
||||
from twitch.models import Channel
|
||||
from twitch.models import ChatBadge
|
||||
from twitch.models import ChatBadgeSet
|
||||
|
|
@ -1321,19 +1324,61 @@ class TestSitemapView:
|
|||
name="ch1",
|
||||
display_name="Channel 1",
|
||||
)
|
||||
now: datetime = timezone.now()
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
description="Desc",
|
||||
game=game,
|
||||
operation_names=["DropCampaignDetails"],
|
||||
start_at=now - datetime.timedelta(days=1),
|
||||
end_at=now + datetime.timedelta(days=1),
|
||||
)
|
||||
inactive_campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp2",
|
||||
name="Inactive Campaign",
|
||||
description="Desc",
|
||||
game=game,
|
||||
operation_names=["DropCampaignDetails"],
|
||||
start_at=now - datetime.timedelta(days=10),
|
||||
end_at=now - datetime.timedelta(days=5),
|
||||
)
|
||||
|
||||
kick_org: KickOrganization = KickOrganization.objects.create(
|
||||
kick_id="org1",
|
||||
name="Kick Org",
|
||||
)
|
||||
kick_cat: KickCategory = KickCategory.objects.create(
|
||||
kick_id=1,
|
||||
name="Kick Game",
|
||||
slug="kick-game",
|
||||
)
|
||||
kick_active: KickDropCampaign = KickDropCampaign.objects.create(
|
||||
kick_id="kcamp1",
|
||||
name="Kick Active Campaign",
|
||||
organization=kick_org,
|
||||
category=kick_cat,
|
||||
starts_at=now - datetime.timedelta(days=1),
|
||||
ends_at=now + datetime.timedelta(days=1),
|
||||
)
|
||||
kick_inactive: KickDropCampaign = KickDropCampaign.objects.create(
|
||||
kick_id="kcamp2",
|
||||
name="Kick Inactive Campaign",
|
||||
organization=kick_org,
|
||||
category=kick_cat,
|
||||
starts_at=now - datetime.timedelta(days=10),
|
||||
ends_at=now - datetime.timedelta(days=5),
|
||||
)
|
||||
|
||||
badge: ChatBadgeSet = ChatBadgeSet.objects.create(set_id="badge1")
|
||||
return {
|
||||
"org": org,
|
||||
"game": game,
|
||||
"channel": channel,
|
||||
"campaign": campaign,
|
||||
"inactive_campaign": inactive_campaign,
|
||||
"kick_active": kick_active,
|
||||
"kick_inactive": kick_inactive,
|
||||
"badge": badge,
|
||||
}
|
||||
|
||||
|
|
@ -1357,16 +1402,24 @@ class TestSitemapView:
|
|||
content = response.content.decode()
|
||||
assert content.startswith('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
|
||||
def test_sitemap_contains_urlset(
|
||||
def test_sitemap_contains_sitemap_index(
|
||||
self,
|
||||
client: Client,
|
||||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test sitemap contains urlset element."""
|
||||
"""Test sitemap index contains sitemap locations."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
content: str = response.content.decode()
|
||||
assert "<urlset" in content
|
||||
assert "</urlset>" in content
|
||||
assert "<sitemapindex" in content
|
||||
assert "</sitemapindex>" in content
|
||||
assert "/sitemap-static.xml" in content
|
||||
assert "/sitemap-twitch-channels.xml" in content
|
||||
assert "/sitemap-twitch-drops.xml" in content
|
||||
assert "/sitemap-kick.xml" in content
|
||||
assert "/sitemap-youtube.xml" in content
|
||||
|
||||
# Ensure at least one entry includes a lastmod (there are entities created by the fixture)
|
||||
assert "<lastmod>" in content
|
||||
|
||||
def test_sitemap_contains_static_pages(
|
||||
self,
|
||||
|
|
@ -1374,15 +1427,19 @@ class TestSitemapView:
|
|||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test sitemap includes static pages."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-static.xml")
|
||||
content: str = response.content.decode()
|
||||
# Check for some static pages
|
||||
|
||||
# Check for the homepage and a few key list views across apps.
|
||||
assert (
|
||||
"<loc>http://testserver/</loc>" in content
|
||||
or "<loc>http://localhost:8000/</loc>" in content
|
||||
)
|
||||
assert "/campaigns/" in content
|
||||
assert "/games/" in content
|
||||
assert "http://testserver/twitch/" in content
|
||||
assert "http://testserver/kick/" in content
|
||||
assert "http://testserver/youtube/" in content
|
||||
assert "http://testserver/twitch/campaigns/" in content
|
||||
assert "http://testserver/twitch/games/" in content
|
||||
|
||||
def test_sitemap_contains_game_detail_pages(
|
||||
self,
|
||||
|
|
@ -1391,7 +1448,7 @@ class TestSitemapView:
|
|||
) -> None:
|
||||
"""Test sitemap includes game detail pages."""
|
||||
game: Game = sample_entities["game"]
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-others.xml")
|
||||
content: str = response.content.decode()
|
||||
assert f"/games/{game.twitch_id}/" in content
|
||||
|
||||
|
|
@ -1402,10 +1459,62 @@ class TestSitemapView:
|
|||
) -> None:
|
||||
"""Test sitemap includes campaign detail pages."""
|
||||
campaign: DropCampaign = sample_entities["campaign"]
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-drops.xml")
|
||||
content: str = response.content.decode()
|
||||
assert f"/campaigns/{campaign.twitch_id}/" in content
|
||||
|
||||
def test_sitemap_prioritizes_active_drops(
|
||||
self,
|
||||
client: Client,
|
||||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test active drops are prioritised and crawled more frequently."""
|
||||
active_campaign: DropCampaign = sample_entities["campaign"]
|
||||
inactive_campaign: DropCampaign = sample_entities["inactive_campaign"]
|
||||
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-drops.xml")
|
||||
content: str = response.content.decode()
|
||||
|
||||
active_loc: str = f"<loc>http://testserver/twitch/campaigns/{active_campaign.twitch_id}/</loc>"
|
||||
active_index: int = content.find(active_loc)
|
||||
assert active_index != -1
|
||||
active_end: int = content.find("</url>", active_index)
|
||||
assert active_end != -1
|
||||
|
||||
inactive_loc: str = f"<loc>http://testserver/twitch/campaigns/{inactive_campaign.twitch_id}/</loc>"
|
||||
inactive_index: int = content.find(inactive_loc)
|
||||
assert inactive_index != -1
|
||||
inactive_end: int = content.find("</url>", inactive_index)
|
||||
assert inactive_end != -1
|
||||
|
||||
def test_sitemap_prioritizes_active_kick_campaigns(
|
||||
self,
|
||||
client: Client,
|
||||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test active Kick campaigns are prioritised and crawled more frequently."""
|
||||
active_campaign: KickDropCampaign = sample_entities["kick_active"]
|
||||
inactive_campaign: KickDropCampaign = sample_entities["kick_inactive"]
|
||||
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-kick.xml")
|
||||
content: str = response.content.decode()
|
||||
|
||||
active_loc: str = (
|
||||
f"<loc>http://testserver/kick/campaigns/{active_campaign.kick_id}/</loc>"
|
||||
)
|
||||
active_index: int = content.find(active_loc)
|
||||
assert active_index != -1
|
||||
active_end: int = content.find("</url>", active_index)
|
||||
assert active_end != -1
|
||||
|
||||
inactive_loc: str = (
|
||||
f"<loc>http://testserver/kick/campaigns/{inactive_campaign.kick_id}/</loc>"
|
||||
)
|
||||
inactive_index: int = content.find(inactive_loc)
|
||||
assert inactive_index != -1
|
||||
inactive_end: int = content.find("</url>", inactive_index)
|
||||
assert inactive_end != -1
|
||||
|
||||
def test_sitemap_contains_organization_detail_pages(
|
||||
self,
|
||||
client: Client,
|
||||
|
|
@ -1413,7 +1522,7 @@ class TestSitemapView:
|
|||
) -> None:
|
||||
"""Test sitemap includes organization detail pages."""
|
||||
org: Organization = sample_entities["org"]
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-others.xml")
|
||||
content: str = response.content.decode()
|
||||
assert f"/organizations/{org.twitch_id}/" in content
|
||||
|
||||
|
|
@ -1424,7 +1533,9 @@ class TestSitemapView:
|
|||
) -> None:
|
||||
"""Test sitemap includes channel detail pages."""
|
||||
channel: Channel = sample_entities["channel"]
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get(
|
||||
"/sitemap-twitch-channels.xml",
|
||||
)
|
||||
content: str = response.content.decode()
|
||||
assert f"/twitch/channels/{channel.twitch_id}/" in content
|
||||
|
||||
|
|
@ -1435,31 +1546,19 @@ class TestSitemapView:
|
|||
) -> None:
|
||||
"""Test sitemap includes badge detail pages."""
|
||||
badge: ChatBadge = sample_entities["badge"]
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-others.xml")
|
||||
content: str = response.content.decode()
|
||||
assert f"/badges/{badge.set_id}/" in content # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
def test_sitemap_includes_priority(
|
||||
def test_sitemap_contains_youtube_pages(
|
||||
self,
|
||||
client: Client,
|
||||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test sitemap includes priority values."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
"""Test sitemap includes YouTube landing page."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-youtube.xml")
|
||||
content: str = response.content.decode()
|
||||
assert "<priority>" in content
|
||||
assert "</priority>" in content
|
||||
|
||||
def test_sitemap_includes_changefreq(
|
||||
self,
|
||||
client: Client,
|
||||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test sitemap includes changefreq values."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
content: str = response.content.decode()
|
||||
assert "<changefreq>" in content
|
||||
assert "</changefreq>" in content
|
||||
assert "/youtube/" in content
|
||||
|
||||
def test_sitemap_includes_lastmod(
|
||||
self,
|
||||
|
|
@ -1467,7 +1566,7 @@ class TestSitemapView:
|
|||
sample_entities: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test sitemap includes lastmod for detail pages."""
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap.xml")
|
||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-others.xml")
|
||||
content: str = response.content.decode()
|
||||
# Check for lastmod in game or campaign entries
|
||||
assert "<lastmod>" in content
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue