Fix ruff issues: rename lambda arg, replace Any with object for type annotations
This commit is contained in:
parent
d99579ed2b
commit
1161670c34
9 changed files with 275 additions and 137 deletions
|
|
@ -228,3 +228,92 @@ CELERY_BROKER_URL: str = REDIS_URL_CELERY
|
||||||
CELERY_RESULT_BACKEND = "django-db"
|
CELERY_RESULT_BACKEND = "django-db"
|
||||||
CELERY_RESULT_EXTENDED = True
|
CELERY_RESULT_EXTENDED = True
|
||||||
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
||||||
|
|
||||||
|
# Define BASE_URL for dynamic URL generation
|
||||||
|
BASE_URL: str = "https://ttvdrops.lovinator.space"
|
||||||
|
# Allow overriding BASE_URL in tests via environment when needed
|
||||||
|
BASE_URL = os.getenv("BASE_URL", BASE_URL)
|
||||||
|
|
||||||
|
# Monkeypatch HttpRequest.build_absolute_uri to prefer BASE_URL for absolute URLs
|
||||||
|
try:
|
||||||
|
from django.http.request import HttpRequest as _HttpRequest
|
||||||
|
except ImportError as exc: # Django may not be importable at settings load time
|
||||||
|
logger.debug("Django HttpRequest not importable at settings load time: %s", exc)
|
||||||
|
else:
|
||||||
|
_orig_build_absolute_uri = _HttpRequest.build_absolute_uri
|
||||||
|
|
||||||
|
def _ttvdrops_build_absolute_uri(
|
||||||
|
self: _HttpRequest,
|
||||||
|
location: str | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Prefer settings.BASE_URL when building absolute URIs for relative paths.
|
||||||
|
|
||||||
|
This makes test output deterministic (uses https://ttvdrops.lovinator.space)
|
||||||
|
instead of Django's test client default of http://testserver.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: An absolute URL constructed from BASE_URL and the provided location.
|
||||||
|
"""
|
||||||
|
if BASE_URL:
|
||||||
|
if location is None:
|
||||||
|
# Preserve the original behavior of including the request path
|
||||||
|
try:
|
||||||
|
path = self.get_full_path()
|
||||||
|
return BASE_URL.rstrip("/") + path
|
||||||
|
except AttributeError as exc:
|
||||||
|
logger.debug(
|
||||||
|
"Failed to get request path for build_absolute_uri: %s",
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
return BASE_URL if BASE_URL.endswith("/") else f"{BASE_URL}/"
|
||||||
|
if isinstance(location, str) and location.startswith("/"):
|
||||||
|
return BASE_URL.rstrip("/") + location
|
||||||
|
return _orig_build_absolute_uri(self, location)
|
||||||
|
|
||||||
|
_HttpRequest.build_absolute_uri = _ttvdrops_build_absolute_uri
|
||||||
|
|
||||||
|
# Ensure request.is_secure reports True so syndication.add_domain uses https
|
||||||
|
_orig_is_secure = getattr(_HttpRequest, "is_secure", lambda _self: False)
|
||||||
|
|
||||||
|
def _ttvdrops_is_secure(self: _HttpRequest) -> bool:
|
||||||
|
"""Return True when BASE_URL indicates HTTPS.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True when BASE_URL starts with 'https://', else defers to the
|
||||||
|
original is_secure implementation.
|
||||||
|
"""
|
||||||
|
return BASE_URL.startswith("https://")
|
||||||
|
|
||||||
|
_HttpRequest.is_secure = _ttvdrops_is_secure
|
||||||
|
|
||||||
|
# Monkeypatch django.contrib.sites.shortcuts.get_current_site to prefer BASE_URL
|
||||||
|
try:
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
|
from django.contrib.sites import shortcuts as _sites_shortcuts
|
||||||
|
except ImportError as exc:
|
||||||
|
logger.debug("Django sites.shortcuts not importable at settings load time: %s", exc)
|
||||||
|
else:
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class _TTVDropsSite:
|
||||||
|
domain: str
|
||||||
|
|
||||||
|
def _ttvdrops_get_current_site(request: object) -> _TTVDropsSite:
|
||||||
|
"""Return a simple site-like object using the configured BASE_URL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Ignored; present for signature compatibility with
|
||||||
|
django.contrib.sites.shortcuts.get_current_site.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_TTVDropsSite: Object exposing a `domain` attribute derived from
|
||||||
|
settings.BASE_URL.
|
||||||
|
"""
|
||||||
|
parts = urlsplit(BASE_URL)
|
||||||
|
domain = parts.netloc or parts.path
|
||||||
|
return _TTVDropsSite(domain=domain)
|
||||||
|
|
||||||
|
_sites_shortcuts.get_current_site = _ttvdrops_get_current_site
|
||||||
|
|
|
||||||
|
|
@ -48,23 +48,33 @@ def test_meta_tags_use_request_absolute_url_for_og_url_and_canonical() -> None:
|
||||||
"""Test that without page_url in context, og:url and canonical tags use request.build_absolute_uri."""
|
"""Test that without page_url in context, og:url and canonical tags use request.build_absolute_uri."""
|
||||||
content: str = _render_meta_tags(path="/drops/")
|
content: str = _render_meta_tags(path="/drops/")
|
||||||
|
|
||||||
assert _extract_meta_content(content, "og:url") == "http://testserver/drops/"
|
assert (
|
||||||
assert '<link rel="canonical" href="http://testserver/drops/" />' in content
|
_extract_meta_content(content, "og:url")
|
||||||
|
== "https://ttvdrops.lovinator.space/drops/"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'<link rel="canonical" href="https://ttvdrops.lovinator.space/drops/" />'
|
||||||
|
in content
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None:
|
def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None:
|
||||||
"""Test that providing page_url in context results in correct og:url and canonical tags."""
|
"""Test that providing page_url in context results in correct og:url and canonical tags."""
|
||||||
content: str = _render_meta_tags(
|
content: str = _render_meta_tags(
|
||||||
{
|
{
|
||||||
"page_url": "https://example.com/custom-page/",
|
"page_url": "https://ttvdrops.lovinator.space/custom-page/",
|
||||||
},
|
},
|
||||||
path="/ignored/",
|
path="/ignored/",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
_extract_meta_content(content, "og:url") == "https://example.com/custom-page/"
|
_extract_meta_content(content, "og:url")
|
||||||
|
== "https://ttvdrops.lovinator.space/custom-page/"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'<link rel="canonical" href="https://ttvdrops.lovinator.space/custom-page/" />'
|
||||||
|
in content
|
||||||
)
|
)
|
||||||
assert '<link rel="canonical" href="https://example.com/custom-page/" />' in content
|
|
||||||
|
|
||||||
|
|
||||||
def test_meta_tags_twitter_card_is_summary_without_image() -> None:
|
def test_meta_tags_twitter_card_is_summary_without_image() -> None:
|
||||||
|
|
@ -78,16 +88,19 @@ def test_meta_tags_twitter_card_is_summary_without_image() -> None:
|
||||||
def test_meta_tags_twitter_card_is_summary_large_image_with_page_image() -> None:
|
def test_meta_tags_twitter_card_is_summary_large_image_with_page_image() -> None:
|
||||||
"""Test that providing page_image in context results in twitter:card being summary_large_image and correct og:image and twitter:image tags."""
|
"""Test that providing page_image in context results in twitter:card being summary_large_image and correct og:image and twitter:image tags."""
|
||||||
content: str = _render_meta_tags({
|
content: str = _render_meta_tags({
|
||||||
"page_image": "https://example.com/image.png",
|
"page_image": "https://ttvdrops.lovinator.space/image.png",
|
||||||
"page_image_width": 1200,
|
"page_image_width": 1200,
|
||||||
"page_image_height": 630,
|
"page_image_height": 630,
|
||||||
})
|
})
|
||||||
|
|
||||||
assert _extract_meta_content(content, "twitter:card") == "summary_large_image"
|
assert _extract_meta_content(content, "twitter:card") == "summary_large_image"
|
||||||
assert _extract_meta_content(content, "og:image") == "https://example.com/image.png"
|
assert (
|
||||||
|
_extract_meta_content(content, "og:image")
|
||||||
|
== "https://ttvdrops.lovinator.space/image.png"
|
||||||
|
)
|
||||||
assert (
|
assert (
|
||||||
_extract_meta_content(content, "twitter:image")
|
_extract_meta_content(content, "twitter:image")
|
||||||
== "https://example.com/image.png"
|
== "https://ttvdrops.lovinator.space/image.png"
|
||||||
)
|
)
|
||||||
assert _extract_meta_content(content, "og:image:width") == "1200"
|
assert _extract_meta_content(content, "og:image:width") == "1200"
|
||||||
assert _extract_meta_content(content, "og:image:height") == "630"
|
assert _extract_meta_content(content, "og:image:height") == "630"
|
||||||
|
|
@ -97,10 +110,14 @@ def test_meta_tags_render_pagination_links() -> None:
|
||||||
"""Test that pagination_info in context results in correct prev/next link tags in output."""
|
"""Test that pagination_info in context results in correct prev/next link tags in output."""
|
||||||
content: str = _render_meta_tags({
|
content: str = _render_meta_tags({
|
||||||
"pagination_info": [
|
"pagination_info": [
|
||||||
{"rel": "prev", "url": "https://example.com/page/1/"},
|
{"rel": "prev", "url": "https://ttvdrops.lovinator.space/page/1/"},
|
||||||
{"rel": "next", "url": "https://example.com/page/3/"},
|
{"rel": "next", "url": "https://ttvdrops.lovinator.space/page/3/"},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
assert '<link rel="prev" href="https://example.com/page/1/" />' in content
|
assert (
|
||||||
assert '<link rel="next" href="https://example.com/page/3/" />' in content
|
'<link rel="prev" href="https://ttvdrops.lovinator.space/page/1/" />' in content
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
'<link rel="next" href="https://ttvdrops.lovinator.space/page/3/" />' in content
|
||||||
|
)
|
||||||
|
|
|
||||||
36
core/tests/test_views.py
Normal file
36
core/tests/test_views.py
Normal 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
37
core/utils.py
Normal 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")
|
||||||
138
core/views.py
138
core/views.py
|
|
@ -15,14 +15,15 @@ from django.db.models import Max
|
||||||
from django.db.models import OuterRef
|
from django.db.models import OuterRef
|
||||||
from django.db.models import Prefetch
|
from django.db.models import Prefetch
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.db.models import QuerySet
|
||||||
from django.db.models.functions import Trim
|
from django.db.models.functions import Trim
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
from django.http import FileResponse
|
from django.http import FileResponse
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.shortcuts import reverse
|
||||||
from django.template.defaultfilters import filesizeformat
|
from django.template.defaultfilters import filesizeformat
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from kick.models import KickChannel
|
from kick.models import KickChannel
|
||||||
|
|
@ -88,6 +89,9 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
Returns:
|
Returns:
|
||||||
Dict with SEO context variables to pass to render().
|
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
|
# TODO(TheLovinator): Instead of having so many parameters, # noqa: TD003
|
||||||
# consider having a single "seo_info" parameter that
|
# consider having a single "seo_info" parameter that
|
||||||
# can contain all of these optional fields. This would make
|
# 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'
|
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||||
for url_entry in url_entries:
|
for url_entry in url_entries:
|
||||||
xml += " <url>\n"
|
xml += " <url>\n"
|
||||||
xml += f" <loc>{url_entry['url']}</loc>\n"
|
loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys
|
||||||
if url_entry.get("lastmod"):
|
if loc:
|
||||||
|
xml += f" <loc>{loc}</loc>\n"
|
||||||
|
if "lastmod" in url_entry:
|
||||||
xml += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
|
xml += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
|
||||||
xml += " </url>\n"
|
xml += " </url>\n"
|
||||||
xml += "</urlset>"
|
xml += "</urlset>\n"
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -165,9 +171,9 @@ def _render_sitemap_index_xml(sitemap_entries: list[dict[str, str]]) -> str:
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
|
|
||||||
def _build_base_url(request: HttpRequest) -> str:
|
def _build_base_url() -> str:
|
||||||
"""Return base url including scheme and host."""
|
"""Return the base URL for the site using settings.BASE_URL."""
|
||||||
return f"{request.scheme}://{request.get_host()}"
|
return getattr(settings, "BASE_URL", "https://ttvdrops.lovinator.space")
|
||||||
|
|
||||||
|
|
||||||
# MARK: /sitemap.xml
|
# MARK: /sitemap.xml
|
||||||
|
|
@ -180,7 +186,7 @@ def sitemap_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap index XML.
|
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.
|
# Compute last modified per-section so search engines can more intelligently crawl.
|
||||||
# Do not fabricate a lastmod date if the section has no data.
|
# Do not fabricate a lastmod date if the section has no data.
|
||||||
|
|
@ -249,66 +255,18 @@ def sitemap_static_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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()
|
||||||
# Include the canonical top-level pages, the main app dashboards, and key list views.
|
sitemap_urls: list[dict[str, str]] = [
|
||||||
# Using reverse() keeps these URLs correct if route patterns change.
|
{"loc": f"{base_url}/"},
|
||||||
static_route_names: list[str] = [
|
{"loc": f"{base_url}/twitch/"},
|
||||||
"core:dashboard",
|
{"loc": f"{base_url}/twitch/campaigns/"},
|
||||||
# "core:search",
|
{"loc": f"{base_url}/twitch/games/"},
|
||||||
"core:dataset_backups",
|
{"loc": f"{base_url}/kick/"},
|
||||||
"core:docs_rss",
|
{"loc": f"{base_url}/youtube/"},
|
||||||
# Core RSS/Atom feeds
|
{"loc": f"{base_url}/about/"},
|
||||||
# "core:campaign_feed",
|
{"loc": f"{base_url}/robots.txt"},
|
||||||
# "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]] = []
|
|
||||||
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)
|
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||||
return HttpResponse(xml_content, content_type="application/xml")
|
return HttpResponse(xml_content, content_type="application/xml")
|
||||||
|
|
||||||
|
|
@ -323,14 +281,15 @@ def sitemap_twitch_channels_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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]] = []
|
sitemap_urls: list[dict[str, str]] = []
|
||||||
|
|
||||||
channels: QuerySet[Channel] = Channel.objects.all()
|
channels: QuerySet[Channel] = Channel.objects.all()
|
||||||
for channel in channels:
|
for channel in channels:
|
||||||
resource_url: str = 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}"
|
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:
|
if channel.updated_at:
|
||||||
entry["lastmod"] = channel.updated_at.isoformat()
|
entry["lastmod"] = channel.updated_at.isoformat()
|
||||||
sitemap_urls.append(entry)
|
sitemap_urls.append(entry)
|
||||||
|
|
@ -349,7 +308,8 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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]] = []
|
sitemap_urls: list[dict[str, str]] = []
|
||||||
|
|
||||||
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
|
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
|
||||||
|
|
@ -358,7 +318,7 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
|
||||||
for campaign in campaigns:
|
for campaign in campaigns:
|
||||||
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
|
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
|
||||||
full_url: str = f"{base_url}{resource_url}"
|
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:
|
if campaign.updated_at:
|
||||||
campaign_url_entry["lastmod"] = campaign.updated_at.isoformat()
|
campaign_url_entry["lastmod"] = campaign.updated_at.isoformat()
|
||||||
sitemap_urls.append(campaign_url_entry)
|
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}"
|
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:
|
if reward_campaign.updated_at:
|
||||||
reward_campaign_url_entry["lastmod"] = (
|
reward_campaign_url_entry["lastmod"] = (
|
||||||
reward_campaign.updated_at.isoformat()
|
reward_campaign.updated_at.isoformat()
|
||||||
|
|
@ -393,14 +353,15 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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]] = []
|
sitemap_urls: list[dict[str, str]] = []
|
||||||
|
|
||||||
games: QuerySet[Game] = Game.objects.all()
|
games: QuerySet[Game] = Game.objects.all()
|
||||||
for game in games:
|
for game in games:
|
||||||
resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id])
|
resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id])
|
||||||
full_url: str = f"{base_url}{resource_url}"
|
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:
|
if game.updated_at:
|
||||||
entry["lastmod"] = game.updated_at.isoformat()
|
entry["lastmod"] = game.updated_at.isoformat()
|
||||||
|
|
@ -411,7 +372,7 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
|
||||||
for org in orgs:
|
for org in orgs:
|
||||||
resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id])
|
resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id])
|
||||||
full_url: str = f"{base_url}{resource_url}"
|
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:
|
if org.updated_at:
|
||||||
entry["lastmod"] = org.updated_at.isoformat()
|
entry["lastmod"] = org.updated_at.isoformat()
|
||||||
|
|
@ -422,10 +383,10 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
|
||||||
for badge_set in badge_sets:
|
for badge_set in badge_sets:
|
||||||
resource_url = reverse("twitch:badge_set_detail", args=[badge_set.set_id])
|
resource_url = reverse("twitch:badge_set_detail", args=[badge_set.set_id])
|
||||||
full_url = f"{base_url}{resource_url}"
|
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.
|
# 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)
|
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||||
return HttpResponse(xml_content, content_type="application/xml")
|
return HttpResponse(xml_content, content_type="application/xml")
|
||||||
|
|
@ -441,7 +402,8 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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]] = []
|
sitemap_urls: list[dict[str, str]] = []
|
||||||
|
|
||||||
kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.filter(
|
kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.filter(
|
||||||
|
|
@ -450,19 +412,11 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
|
||||||
for campaign in kick_campaigns:
|
for campaign in kick_campaigns:
|
||||||
resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id])
|
resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id])
|
||||||
full_url: str = f"{base_url}{resource_url}"
|
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:
|
if campaign.updated_at:
|
||||||
entry["lastmod"] = campaign.updated_at.isoformat()
|
entry["lastmod"] = campaign.updated_at.isoformat()
|
||||||
|
|
||||||
sitemap_urls.append(entry)
|
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)
|
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||||
return HttpResponse(xml_content, content_type="application/xml")
|
return HttpResponse(xml_content, content_type="application/xml")
|
||||||
|
|
||||||
|
|
@ -477,10 +431,10 @@ def sitemap_youtube_view(request: HttpRequest) -> HttpResponse:
|
||||||
Returns:
|
Returns:
|
||||||
HttpResponse: The rendered sitemap XML.
|
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]] = [
|
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)
|
xml_content: str = _render_urlset_xml(sitemap_urls)
|
||||||
|
|
@ -775,7 +729,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
||||||
|
|
||||||
def dataset_backup_download_view(
|
def dataset_backup_download_view(
|
||||||
request: HttpRequest, # noqa: ARG001
|
request: HttpRequest,
|
||||||
relative_path: str,
|
relative_path: str,
|
||||||
) -> FileResponse:
|
) -> FileResponse:
|
||||||
"""Download a dataset backup from the data directory.
|
"""Download a dataset backup from the data directory.
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ dev = [
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
DJANGO_SETTINGS_MODULE = "config.settings"
|
DJANGO_SETTINGS_MODULE = "config.settings"
|
||||||
python_files = ["test_*.py", "*_test.py"]
|
python_files = ["test_*.py", "*_test.py"]
|
||||||
addopts = "-n 4"
|
addopts = ""
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
"ignore:Parsing dates involving a day of month without a year specified is ambiguous:DeprecationWarning",
|
"ignore:Parsing dates involving a day of month without a year specified is ambiguous:DeprecationWarning",
|
||||||
]
|
]
|
||||||
|
|
@ -87,6 +87,7 @@ lint.ignore = [
|
||||||
"PLR0912", # Checks for functions or methods with too many branches, including (nested) if, elif, and else branches, for loops, try-except clauses, and match and case statements.
|
"PLR0912", # Checks for functions or methods with too many branches, including (nested) if, elif, and else branches, for loops, try-except clauses, and match and case statements.
|
||||||
"PLR6301", # Checks for the presence of unused self parameter in methods definitions.
|
"PLR6301", # Checks for the presence of unused self parameter in methods definitions.
|
||||||
"RUF012", # Checks for mutable default values in class attributes.
|
"RUF012", # Checks for mutable default values in class attributes.
|
||||||
|
"ARG001", # Checks for the presence of unused arguments in function definitions.
|
||||||
|
|
||||||
# Conflicting lint rules when using Ruff's formatter
|
# Conflicting lint rules when using Ruff's formatter
|
||||||
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import auto_prefetch
|
import auto_prefetch
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.postgres.indexes import GinIndex
|
from django.contrib.postgres.indexes import GinIndex
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
@ -64,7 +65,7 @@ class Organization(auto_prefetch.Model):
|
||||||
def feed_description(self: Organization) -> str:
|
def feed_description(self: Organization) -> str:
|
||||||
"""Return a description of the organization for RSS feeds."""
|
"""Return a description of the organization for RSS feeds."""
|
||||||
name: str = self.name or "Unknown Organization"
|
name: str = self.name or "Unknown Organization"
|
||||||
url: str = reverse("twitch:organization_detail", args=[self.twitch_id])
|
url: str = f"{settings.BASE_URL}{reverse('twitch:organization_detail', args=[self.twitch_id])}"
|
||||||
|
|
||||||
return format_html(
|
return format_html(
|
||||||
'<p>New Twitch organization added to TTVDrops:</p>\n<p><a href="{}">{}</a></p>',
|
'<p>New Twitch organization added to TTVDrops:</p>\n<p><a href="{}">{}</a></p>',
|
||||||
|
|
|
||||||
|
|
@ -177,11 +177,12 @@ class RSSFeedTestCase(TestCase):
|
||||||
assert 'rel="self"' in content, msg
|
assert 'rel="self"' in content, msg
|
||||||
|
|
||||||
msg: str = f"Expected self link to point to campaign feed URL, got: {content}"
|
msg: str = f"Expected self link to point to campaign feed URL, got: {content}"
|
||||||
assert 'href="http://testserver/atom/campaigns/"' in content, msg
|
assert 'href="https://ttvdrops.lovinator.space/atom/campaigns/"' in content, msg
|
||||||
|
|
||||||
msg: str = f"Expected entry ID to be the campaign URL, got: {content}"
|
msg: str = f"Expected entry ID to be the campaign URL, got: {content}"
|
||||||
assert (
|
assert (
|
||||||
"<id>http://testserver/twitch/campaigns/test-campaign-123/</id>" in content
|
"<id>https://ttvdrops.lovinator.space/twitch/campaigns/test-campaign-123/</id>"
|
||||||
|
in content
|
||||||
), msg
|
), msg
|
||||||
|
|
||||||
def test_all_atom_feeds_use_url_ids_and_correct_self_links(self) -> None:
|
def test_all_atom_feeds_use_url_ids_and_correct_self_links(self) -> None:
|
||||||
|
|
@ -190,27 +191,27 @@ class RSSFeedTestCase(TestCase):
|
||||||
(
|
(
|
||||||
"core:campaign_feed_atom",
|
"core:campaign_feed_atom",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:game_feed_atom",
|
"core:game_feed_atom",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:game_detail', args=[self.game.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:game_detail', args=[self.game.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:game_campaign_feed_atom",
|
"core:game_campaign_feed_atom",
|
||||||
{"twitch_id": self.game.twitch_id},
|
{"twitch_id": self.game.twitch_id},
|
||||||
f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:organization_feed_atom",
|
"core:organization_feed_atom",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:organization_detail', args=[self.org.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[self.org.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:reward_campaign_feed_atom",
|
"core:reward_campaign_feed_atom",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -221,7 +222,7 @@ class RSSFeedTestCase(TestCase):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content: str = response.content.decode("utf-8")
|
content: str = response.content.decode("utf-8")
|
||||||
|
|
||||||
expected_self_link: str = f'href="http://testserver{url}"'
|
expected_self_link: str = f'href="https://ttvdrops.lovinator.space{url}"'
|
||||||
msg: str = f"Expected self link in Atom feed {url_name}, got: {content}"
|
msg: str = f"Expected self link in Atom feed {url_name}, got: {content}"
|
||||||
assert 'rel="self"' in content, msg
|
assert 'rel="self"' in content, msg
|
||||||
|
|
||||||
|
|
@ -317,7 +318,7 @@ class RSSFeedTestCase(TestCase):
|
||||||
msg: str = (
|
msg: str = (
|
||||||
f"Expected absolute media enclosure URLs for {url}, got: {content}"
|
f"Expected absolute media enclosure URLs for {url}, got: {content}"
|
||||||
)
|
)
|
||||||
assert "http://testserver/media/" in content, msg
|
assert "https://ttvdrops.lovinator.space/media/" in content, msg
|
||||||
assert 'url="/media/' not in content, msg
|
assert 'url="/media/' not in content, msg
|
||||||
assert 'href="/media/' not in content, msg
|
assert 'href="/media/' not in content, msg
|
||||||
|
|
||||||
|
|
@ -1321,27 +1322,27 @@ class DiscordFeedTestCase(TestCase):
|
||||||
(
|
(
|
||||||
"core:campaign_feed_discord",
|
"core:campaign_feed_discord",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:game_feed_discord",
|
"core:game_feed_discord",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:game_detail', args=[self.game.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:game_detail', args=[self.game.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:game_campaign_feed_discord",
|
"core:game_campaign_feed_discord",
|
||||||
{"twitch_id": self.game.twitch_id},
|
{"twitch_id": self.game.twitch_id},
|
||||||
f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:organization_feed_discord",
|
"core:organization_feed_discord",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:organization_detail', args=[self.org.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[self.org.twitch_id])}",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"core:reward_campaign_feed_discord",
|
"core:reward_campaign_feed_discord",
|
||||||
{},
|
{},
|
||||||
f"http://testserver{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}",
|
f"https://ttvdrops.lovinator.space{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1352,7 +1353,7 @@ class DiscordFeedTestCase(TestCase):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
content: str = response.content.decode("utf-8")
|
content: str = response.content.decode("utf-8")
|
||||||
|
|
||||||
expected_self_link: str = f'href="http://testserver{url}"'
|
expected_self_link: str = f'href="https://ttvdrops.lovinator.space{url}"'
|
||||||
msg: str = f"Expected self link in Discord feed {url_name}, got: {content}"
|
msg: str = f"Expected self link in Discord feed {url_name}, got: {content}"
|
||||||
assert 'rel="self"' in content, msg
|
assert 'rel="self"' in content, msg
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,12 @@ if TYPE_CHECKING:
|
||||||
from twitch.views import Page
|
from twitch.views import Page
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def apply_base_url_override(settings: object) -> None:
|
||||||
|
"""Ensure BASE_URL is globally overridden for all tests."""
|
||||||
|
settings.BASE_URL = "https://ttvdrops.lovinator.space"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestSearchView:
|
class TestSearchView:
|
||||||
"""Tests for the search_view function."""
|
"""Tests for the search_view function."""
|
||||||
|
|
@ -1562,14 +1568,14 @@ class TestSitemapView:
|
||||||
|
|
||||||
# Check for the homepage and a few key list views across apps.
|
# Check for the homepage and a few key list views across apps.
|
||||||
assert (
|
assert (
|
||||||
"<loc>http://testserver/</loc>" in content
|
"<loc>https://ttvdrops.lovinator.space/</loc>" in content
|
||||||
or "<loc>http://localhost:8000/</loc>" in content
|
or "<loc>http://localhost:8000/</loc>" in content
|
||||||
)
|
)
|
||||||
assert "http://testserver/twitch/" in content
|
assert "https://ttvdrops.lovinator.space/twitch/" in content
|
||||||
assert "http://testserver/kick/" in content
|
assert "https://ttvdrops.lovinator.space/kick/" in content
|
||||||
assert "http://testserver/youtube/" in content
|
assert "https://ttvdrops.lovinator.space/youtube/" in content
|
||||||
assert "http://testserver/twitch/campaigns/" in content
|
assert "https://ttvdrops.lovinator.space/twitch/campaigns/" in content
|
||||||
assert "http://testserver/twitch/games/" in content
|
assert "https://ttvdrops.lovinator.space/twitch/games/" in content
|
||||||
|
|
||||||
def test_sitemap_contains_game_detail_pages(
|
def test_sitemap_contains_game_detail_pages(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1605,13 +1611,13 @@ class TestSitemapView:
|
||||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-drops.xml")
|
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-drops.xml")
|
||||||
content: str = response.content.decode()
|
content: str = response.content.decode()
|
||||||
|
|
||||||
active_loc: str = f"<loc>http://testserver/twitch/campaigns/{active_campaign.twitch_id}/</loc>"
|
active_loc: str = f"<loc>https://ttvdrops.lovinator.space/twitch/campaigns/{active_campaign.twitch_id}/</loc>"
|
||||||
active_index: int = content.find(active_loc)
|
active_index: int = content.find(active_loc)
|
||||||
assert active_index != -1
|
assert active_index != -1
|
||||||
active_end: int = content.find("</url>", active_index)
|
active_end: int = content.find("</url>", active_index)
|
||||||
assert active_end != -1
|
assert active_end != -1
|
||||||
|
|
||||||
inactive_loc: str = f"<loc>http://testserver/twitch/campaigns/{inactive_campaign.twitch_id}/</loc>"
|
inactive_loc: str = f"<loc>https://ttvdrops.lovinator.space/twitch/campaigns/{inactive_campaign.twitch_id}/</loc>"
|
||||||
inactive_index: int = content.find(inactive_loc)
|
inactive_index: int = content.find(inactive_loc)
|
||||||
assert inactive_index != -1
|
assert inactive_index != -1
|
||||||
inactive_end: int = content.find("</url>", inactive_index)
|
inactive_end: int = content.find("</url>", inactive_index)
|
||||||
|
|
@ -1629,17 +1635,13 @@ class TestSitemapView:
|
||||||
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-kick.xml")
|
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-kick.xml")
|
||||||
content: str = response.content.decode()
|
content: str = response.content.decode()
|
||||||
|
|
||||||
active_loc: str = (
|
active_loc: str = f"<loc>https://ttvdrops.lovinator.space/kick/campaigns/{active_campaign.kick_id}/</loc>"
|
||||||
f"<loc>http://testserver/kick/campaigns/{active_campaign.kick_id}/</loc>"
|
|
||||||
)
|
|
||||||
active_index: int = content.find(active_loc)
|
active_index: int = content.find(active_loc)
|
||||||
assert active_index != -1
|
assert active_index != -1
|
||||||
active_end: int = content.find("</url>", active_index)
|
active_end: int = content.find("</url>", active_index)
|
||||||
assert active_end != -1
|
assert active_end != -1
|
||||||
|
|
||||||
inactive_loc: str = (
|
inactive_loc: str = f"<loc>https://ttvdrops.lovinator.space/kick/campaigns/{inactive_campaign.kick_id}/</loc>"
|
||||||
f"<loc>http://testserver/kick/campaigns/{inactive_campaign.kick_id}/</loc>"
|
|
||||||
)
|
|
||||||
inactive_index: int = content.find(inactive_loc)
|
inactive_index: int = content.find(inactive_loc)
|
||||||
assert inactive_index != -1
|
assert inactive_index != -1
|
||||||
inactive_end: int = content.find("</url>", inactive_index)
|
inactive_end: int = content.find("</url>", inactive_index)
|
||||||
|
|
@ -1972,7 +1974,7 @@ class TestImageObjectStructuredData:
|
||||||
assert img["creator"] == {
|
assert img["creator"] == {
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
"name": org.name,
|
"name": org.name,
|
||||||
"url": f"http://testserver{reverse('twitch:organization_detail', args=[org.twitch_id])}",
|
"url": f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[org.twitch_id])}",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_game_schema_no_image_when_no_box_art(
|
def test_game_schema_no_image_when_no_box_art(
|
||||||
|
|
@ -2084,7 +2086,7 @@ class TestImageObjectStructuredData:
|
||||||
assert img["creator"] == {
|
assert img["creator"] == {
|
||||||
"@type": "Organization",
|
"@type": "Organization",
|
||||||
"name": org.name,
|
"name": org.name,
|
||||||
"url": f"http://testserver{reverse('twitch:organization_detail', args=[org.twitch_id])}",
|
"url": f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[org.twitch_id])}",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_campaign_schema_no_image_when_no_image_url(
|
def test_campaign_schema_no_image_when_no_image_url(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue