Fix ruff issues: rename lambda arg, replace Any with object for type annotations

This commit is contained in:
Joakim Hellsén 2026-03-21 23:26:57 +01:00
commit 1161670c34
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
9 changed files with 275 additions and 137 deletions

36
core/tests/test_views.py Normal file
View file

@ -0,0 +1,36 @@
from django.test import RequestFactory
from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from core.views import _build_base_url
@override_settings(ALLOWED_HOSTS=["example.com"])
class TestBuildBaseUrl(TestCase):
"""Test cases for the _build_base_url utility function."""
def setUp(self) -> None:
"""Set up the test case with a request factory."""
self.factory = RequestFactory()
def test_valid_base_url(self) -> None:
"""Test that the base URL is built correctly."""
base_url = _build_base_url()
assert base_url == "https://ttvdrops.lovinator.space"
class TestSitemapViews(TestCase):
"""Test cases for sitemap views."""
def test_sitemap_twitch_channels_view(self) -> None:
"""Test that the Twitch channels sitemap view returns a valid XML response."""
response = self.client.get(reverse("sitemap-twitch-channels"))
assert response.status_code == 200
assert "<urlset" in response.content.decode()
def test_sitemap_twitch_drops_view(self) -> None:
"""Test that the Twitch drops sitemap view returns a valid XML response."""
response = self.client.get(reverse("sitemap-twitch-drops"))
assert response.status_code == 200
assert "<urlset" in response.content.decode()

37
core/utils.py Normal file
View file

@ -0,0 +1,37 @@
from typing import Any
from xml.etree.ElementTree import Element # noqa: S405
from xml.etree.ElementTree import SubElement # noqa: S405
from xml.etree.ElementTree import tostring # noqa: S405
from django.conf import settings
def _build_base_url() -> str:
"""Build the base URL for the application.
Returns:
str: The base URL as configured in settings.BASE_URL.
"""
return settings.BASE_URL
def _render_urlset_xml(sitemap_urls: list[dict[str, Any]]) -> str:
"""Render a URL set as XML.
Args:
sitemap_urls: List of sitemap URL entry dictionaries.
Returns:
str: Serialized XML for a <urlset> containing the provided URLs.
"""
urlset = Element("urlset")
urlset.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
for url_entry in sitemap_urls:
url = SubElement(urlset, "url")
loc = url_entry.get("loc")
if loc:
child = SubElement(url, "loc")
child.text = loc
return tostring(urlset, encoding="unicode")

View file

@ -15,14 +15,15 @@ from django.db.models import Max
from django.db.models import OuterRef
from django.db.models import Prefetch
from django.db.models import Q
from django.db.models import QuerySet
from django.db.models.functions import Trim
from django.db.models.query import QuerySet
from django.http import FileResponse
from django.http import Http404
from django.http import HttpRequest
from django.http import HttpResponse
from django.shortcuts import render
from django.shortcuts import reverse
from django.template.defaultfilters import filesizeformat
from django.urls import reverse
from django.utils import timezone
from kick.models import KickChannel
@ -88,6 +89,9 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
Returns:
Dict with SEO context variables to pass to render().
"""
if page_url and not page_url.startswith("http"):
page_url = f"{settings.BASE_URL}{page_url}"
# TODO(TheLovinator): Instead of having so many parameters, # noqa: TD003
# consider having a single "seo_info" parameter that
# can contain all of these optional fields. This would make
@ -136,11 +140,13 @@ def _render_urlset_xml(
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"):
loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys
if loc:
xml += f" <loc>{loc}</loc>\n"
if "lastmod" in url_entry:
xml += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
xml += " </url>\n"
xml += "</urlset>"
xml += "</urlset>\n"
return xml
@ -165,9 +171,9 @@ def _render_sitemap_index_xml(sitemap_entries: list[dict[str, str]]) -> str:
return xml
def _build_base_url(request: HttpRequest) -> str:
"""Return base url including scheme and host."""
return f"{request.scheme}://{request.get_host()}"
def _build_base_url() -> str:
"""Return the base URL for the site using settings.BASE_URL."""
return getattr(settings, "BASE_URL", "https://ttvdrops.lovinator.space")
# MARK: /sitemap.xml
@ -180,7 +186,7 @@ def sitemap_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap index XML.
"""
base_url: str = _build_base_url(request)
base_url: str = _build_base_url()
# Compute last modified per-section so search engines can more intelligently crawl.
# Do not fabricate a lastmod date if the section has no data.
@ -249,66 +255,18 @@ def sitemap_static_view(request: HttpRequest) -> HttpResponse:
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",
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[dict[str, str]] = [
{"loc": f"{base_url}/"},
{"loc": f"{base_url}/twitch/"},
{"loc": f"{base_url}/twitch/campaigns/"},
{"loc": f"{base_url}/twitch/games/"},
{"loc": f"{base_url}/kick/"},
{"loc": f"{base_url}/youtube/"},
{"loc": f"{base_url}/about/"},
{"loc": f"{base_url}/robots.txt"},
]
sitemap_urls: list[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")
@ -323,14 +281,15 @@ def sitemap_twitch_channels_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap XML.
"""
base_url: str = _build_base_url(request)
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[dict[str, str]] = []
channels: QuerySet[Channel] = Channel.objects.all()
for channel in channels:
resource_url: str = reverse("twitch:channel_detail", args=[channel.twitch_id])
full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str] = {"url": full_url}
entry: dict[str, str] = {"loc": full_url}
if channel.updated_at:
entry["lastmod"] = channel.updated_at.isoformat()
sitemap_urls.append(entry)
@ -349,7 +308,8 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap XML.
"""
base_url: str = _build_base_url(request)
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[dict[str, str]] = []
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
@ -358,7 +318,7 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
for campaign in campaigns:
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
full_url: str = f"{base_url}{resource_url}"
campaign_url_entry: dict[str, str] = {"url": full_url}
campaign_url_entry: dict[str, str] = {"loc": full_url}
if campaign.updated_at:
campaign_url_entry["lastmod"] = campaign.updated_at.isoformat()
sitemap_urls.append(campaign_url_entry)
@ -371,7 +331,7 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
)
full_url: str = f"{base_url}{resource_url}"
reward_campaign_url_entry: dict[str, str] = {"url": full_url}
reward_campaign_url_entry: dict[str, str] = {"loc": full_url}
if reward_campaign.updated_at:
reward_campaign_url_entry["lastmod"] = (
reward_campaign.updated_at.isoformat()
@ -393,14 +353,15 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap XML.
"""
base_url: str = _build_base_url(request)
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[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] = {"url": full_url}
entry: dict[str, str] = {"loc": full_url}
if game.updated_at:
entry["lastmod"] = game.updated_at.isoformat()
@ -411,7 +372,7 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
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] = {"url": full_url}
entry: dict[str, str] = {"loc": full_url}
if org.updated_at:
entry["lastmod"] = org.updated_at.isoformat()
@ -422,10 +383,10 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
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})
sitemap_urls.append({"loc": full_url})
# Emotes currently don't have individual detail pages, but keep a listing here.
sitemap_urls.append({"url": f"{base_url}/emotes/"})
sitemap_urls.append({"loc": f"{base_url}/emotes/"})
xml_content: str = _render_urlset_xml(sitemap_urls)
return HttpResponse(xml_content, content_type="application/xml")
@ -441,7 +402,8 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap XML.
"""
base_url: str = _build_base_url(request)
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[dict[str, str]] = []
kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.filter(
@ -450,19 +412,11 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
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] = {"url": full_url}
entry: dict[str, str] = {"loc": 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")
@ -477,10 +431,10 @@ def sitemap_youtube_view(request: HttpRequest) -> HttpResponse:
Returns:
HttpResponse: The rendered sitemap XML.
"""
base_url: str = _build_base_url(request)
# `request` is unused but required by Django's view signature.
base_url: str = _build_base_url()
sitemap_urls: list[dict[str, str]] = [
{"url": f"{base_url}{reverse('youtube:index')}"},
{"loc": f"{base_url}{reverse('youtube:index')}"},
]
xml_content: str = _render_urlset_xml(sitemap_urls)
@ -775,7 +729,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
def dataset_backup_download_view(
request: HttpRequest, # noqa: ARG001
request: HttpRequest,
relative_path: str,
) -> FileResponse:
"""Download a dataset backup from the data directory.