diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 050a0d8..2c5fb94 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -27,7 +27,7 @@ repos:
args: [--target-version, "6.0"]
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: v0.15.9
+ rev: v0.15.8
hooks:
- id: ruff-check
args: ["--fix", "--exit-non-zero-on-fix"]
diff --git a/chzzk/management/commands/import_chzzk_campaign.py b/chzzk/management/commands/import_chzzk_campaign.py
index c8944ca..3ba11ed 100644
--- a/chzzk/management/commands/import_chzzk_campaign.py
+++ b/chzzk/management/commands/import_chzzk_campaign.py
@@ -1,6 +1,13 @@
from typing import TYPE_CHECKING
from typing import Any
+if TYPE_CHECKING:
+ import argparse
+
+ from chzzk.schemas import ChzzkCampaignV2
+ from chzzk.schemas import ChzzkRewardV2
+
+
import requests
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
@@ -10,12 +17,6 @@ from chzzk.models import ChzzkCampaign
from chzzk.models import ChzzkReward
from chzzk.schemas import ChzzkApiResponseV2
-if TYPE_CHECKING:
- import argparse
-
- from chzzk.schemas import ChzzkCampaignV2
- from chzzk.schemas import ChzzkRewardV2
-
MAX_CAMPAIGN_OUTLIER_THRESHOLD: int = 100_000_000
MAX_CAMPAIGN_OUTLIER_GAP: int = 1_000
diff --git a/config/settings.py b/config/settings.py
index a084435..dbb1285 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -167,7 +167,6 @@ TEMPLATES: list[dict[str, Any]] = [
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
- "core.context_processors.base_url",
],
},
},
@@ -264,3 +263,87 @@ CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
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
diff --git a/config/tests/test_seo.py b/config/tests/test_seo.py
index dcf5411..86f3e85 100644
--- a/config/tests/test_seo.py
+++ b/config/tests/test_seo.py
@@ -52,10 +52,10 @@ def test_meta_tags_use_request_absolute_url_for_og_url_and_canonical() -> None:
_extract_meta_content(content, "og:url")
== "https://ttvdrops.lovinator.space/drops/"
)
- canonical_pattern: re.Pattern[str] = re.compile(
- r'',
+ assert (
+ ''
+ in content
)
- assert canonical_pattern.search(content) is not None
def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None:
@@ -71,10 +71,10 @@ def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None:
_extract_meta_content(content, "og:url")
== "https://ttvdrops.lovinator.space/custom-page/"
)
- canonical_pattern: re.Pattern[str] = re.compile(
- r'',
+ assert (
+ ''
+ in content
)
- assert canonical_pattern.search(content) is not None
def test_meta_tags_twitter_card_is_summary_without_image() -> None:
diff --git a/core/base_url.py b/core/base_url.py
deleted file mode 100644
index 66efa86..0000000
--- a/core/base_url.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from typing import TYPE_CHECKING
-from urllib.parse import urlsplit
-
-from django.conf import settings
-
-if TYPE_CHECKING:
- from django.http import HttpRequest
-
-
-def _get_base_url() -> str:
- """Get normalized BASE_URL from settings.
-
- Returns:
- str: The configured BASE_URL without trailing slash.
- """
- base_url = getattr(settings, "BASE_URL", "")
- return base_url.rstrip("/") if base_url else ""
-
-
-def build_absolute_uri(
- location: str | None = None,
- request: HttpRequest | None = None,
-) -> str:
- """Build an absolute URI via BASE_URL (preferred) or request fallback.
-
- Args:
- location: Relative path ('/foo/') or absolute URL.
- request: Optional HttpRequest to resolve path when location is None.
-
- Returns:
- str: Fully resolved absolute URL.
- """
- base_url = _get_base_url()
-
- if location is None:
- if request is not None:
- location = request.get_full_path()
- else:
- return f"{base_url}/" if base_url else "/"
-
- parsed = urlsplit(location)
- if parsed.scheme and parsed.netloc:
- return location
-
- if base_url:
- if location.startswith("/"):
- return f"{base_url}{location}"
- return f"{base_url}/{location.lstrip('/')}"
-
- if request is not None:
- return request.build_absolute_uri(location)
-
- return location
-
-
-def is_secure() -> bool:
- """Return whether the configured BASE_URL uses HTTPS."""
- base_url = _get_base_url()
- return base_url.startswith("https://") if base_url else False
-
-
-@dataclass
-class _TTVDropsSite:
- domain: str
-
-
-def get_current_site(request: object) -> _TTVDropsSite:
- """Return a site-like object with domain derived from BASE_URL."""
- base_url = _get_base_url()
- parts = urlsplit(base_url)
- domain = parts.netloc or parts.path
- return _TTVDropsSite(domain=domain)
-
-
-def apply_base_url_patches() -> None:
- """No-op; use build_absolute_uri() helper explicitly."""
- return
diff --git a/core/context_processors.py b/core/context_processors.py
deleted file mode 100644
index 66476b8..0000000
--- a/core/context_processors.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from typing import TYPE_CHECKING
-
-from django.conf import settings
-
-if TYPE_CHECKING:
- from django.http import HttpRequest
-
-
-def base_url(request: HttpRequest) -> dict[str, str]:
- """Provide BASE_URL to templates for deterministic absolute URL creation.
-
- Returns:
- dict[str, str]: A dictionary containing the BASE_URL.
- """
- return {
- "BASE_URL": getattr(settings, "BASE_URL", ""),
- }
diff --git a/core/tests/test_base_url.py b/core/tests/test_base_url.py
deleted file mode 100644
index ee0c8c4..0000000
--- a/core/tests/test_base_url.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from typing import TYPE_CHECKING
-
-from django.test import RequestFactory
-
-from core.base_url import build_absolute_uri
-from core.base_url import get_current_site
-
-if TYPE_CHECKING:
- from config.tests.test_seo import WSGIRequest
- from core.base_url import _TTVDropsSite
-
-
-def test_build_absolute_uri_uses_base_url() -> None:
- """Test that build_absolute_uri uses the base URL from settings."""
- request: WSGIRequest = RequestFactory().get("/test-path/")
- assert (
- build_absolute_uri(request=request)
- == "https://ttvdrops.lovinator.space/test-path/"
- )
-
-
-def test_get_current_site_from_base_url() -> None:
- """Test that get_current_site returns the correct site based on the base URL."""
- site: _TTVDropsSite = get_current_site(None)
- assert site.domain == "ttvdrops.lovinator.space"
diff --git a/core/views.py b/core/views.py
index c040c4e..2c6e8b1 100644
--- a/core/views.py
+++ b/core/views.py
@@ -26,7 +26,6 @@ from django.template.defaultfilters import filesizeformat
from django.urls import reverse
from django.utils import timezone
-from core.base_url import build_absolute_uri
from kick.models import KickChannel
from kick.models import KickDropCampaign
from twitch.models import Channel
@@ -508,7 +507,7 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, Any] = _build_seo_context(
page_title="Feed Documentation",
page_description="Documentation for the RSS feeds available on ttvdrops.lovinator.space, including how to use them and what data they contain.",
- page_url=build_absolute_uri(reverse("core:docs_rss")),
+ page_url=request.build_absolute_uri(reverse("core:docs_rss")),
)
return render(
@@ -722,7 +721,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
dataset_distributions.append({
"@type": "DataDownload",
"name": dataset["name"],
- "contentUrl": build_absolute_uri(
+ "contentUrl": request.build_absolute_uri(
reverse("core:dataset_backup_download", args=[download_path]),
),
"encodingFormat": "application/zstd",
@@ -732,9 +731,9 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
"@context": "https://schema.org",
"@type": "Dataset",
"name": "Historical archive of Twitch and Kick drop data",
- "identifier": build_absolute_uri(reverse("core:dataset_backups")),
+ "identifier": request.build_absolute_uri(reverse("core:dataset_backups")),
"temporalCoverage": "2024-07-17/..",
- "url": build_absolute_uri(reverse("core:dataset_backups")),
+ "url": request.build_absolute_uri(reverse("core:dataset_backups")),
"license": "https://creativecommons.org/publicdomain/zero/1.0/",
"isAccessibleForFree": True,
"description": (
@@ -754,7 +753,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
"includedInDataCatalog": {
"@type": "DataCatalog",
"name": "ttvdrops.lovinator.space",
- "url": build_absolute_uri(reverse("core:dataset_backups")),
+ "url": request.build_absolute_uri(reverse("core:dataset_backups")),
},
}
if dataset_distributions:
@@ -906,7 +905,7 @@ def search_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, Any] = _build_seo_context(
page_title=page_title,
page_description=page_description,
- page_url=build_absolute_uri(reverse("core:search")),
+ page_url=request.build_absolute_uri(reverse("core:search")),
)
return render(
request,
@@ -1007,12 +1006,12 @@ def dashboard(request: HttpRequest) -> HttpResponse:
"@context": "https://schema.org",
"@type": "WebSite",
"name": "ttvdrops",
- "url": build_absolute_uri("/"),
+ "url": request.build_absolute_uri("/"),
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
- "urlTemplate": build_absolute_uri(
+ "urlTemplate": request.build_absolute_uri(
"/search/?q={search_term_string}",
),
},
diff --git a/kick/views.py b/kick/views.py
index b8cc126..511447f 100644
--- a/kick/views.py
+++ b/kick/views.py
@@ -15,7 +15,6 @@ from django.shortcuts import render
from django.urls import reverse
from django.utils import timezone
-from core.base_url import build_absolute_uri
from kick.models import KickCategory
from kick.models import KickDropCampaign
from kick.models import KickOrganization
@@ -128,14 +127,14 @@ def _build_pagination_info(
if page_obj.has_previous():
links.append({
"rel": "prev",
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
f"{base_url}{sep}page={page_obj.previous_page_number()}",
),
})
if page_obj.has_next():
links.append({
"rel": "next",
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
f"{base_url}{sep}page={page_obj.next_page_number()}",
),
})
@@ -240,7 +239,7 @@ def campaign_list_view(request: HttpRequest) -> HttpResponse:
seo_context: dict[str, str] = _build_seo_context(
page_title=title,
page_description="Browse Kick drop campaigns.",
- seo_meta={"page_url": build_absolute_uri(base_url)},
+ seo_meta={"page_url": request.build_absolute_uri(base_url)},
)
return render(
request,
@@ -292,20 +291,20 @@ def campaign_detail_view(request: HttpRequest, kick_id: str) -> HttpResponse:
channels: list[KickChannel] = list(campaign.channels.select_related("user"))
breadcrumb_schema: str = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
{
"name": "Kick Campaigns",
- "url": build_absolute_uri(reverse("kick:campaign_list")),
+ "url": request.build_absolute_uri(reverse("kick:campaign_list")),
},
{
"name": campaign.name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("kick:campaign_detail", args=[campaign.kick_id]),
),
},
])
- campaign_url: str = build_absolute_uri(
+ campaign_url: str = request.build_absolute_uri(
reverse("kick:campaign_detail", args=[campaign.kick_id]),
)
campaign_event: dict[str, Any] = {
@@ -432,20 +431,20 @@ def category_detail_view(request: HttpRequest, kick_id: int) -> HttpResponse:
]
breadcrumb_schema: str = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
{
"name": "Kick Games",
- "url": build_absolute_uri(reverse("kick:game_list")),
+ "url": request.build_absolute_uri(reverse("kick:game_list")),
},
{
"name": category.name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("kick:game_detail", args=[category.kick_id]),
),
},
])
- category_url: str = build_absolute_uri(
+ category_url: str = request.build_absolute_uri(
reverse("kick:game_detail", args=[category.kick_id]),
)
category_schema: dict[str, Any] = {
@@ -537,20 +536,20 @@ def organization_detail_view(request: HttpRequest, kick_id: str) -> HttpResponse
)
breadcrumb_schema: str = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
{
"name": "Kick Organizations",
- "url": build_absolute_uri(reverse("kick:organization_list")),
+ "url": request.build_absolute_uri(reverse("kick:organization_list")),
},
{
"name": org.name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("kick:organization_detail", args=[org.kick_id]),
),
},
])
- org_url: str = build_absolute_uri(
+ org_url: str = request.build_absolute_uri(
reverse("kick:organization_detail", args=[org.kick_id]),
)
organization_node: dict[str, Any] = {
diff --git a/templates/includes/meta_tags.html b/templates/includes/meta_tags.html
index 8715692..d90c294 100644
--- a/templates/includes/meta_tags.html
+++ b/templates/includes/meta_tags.html
@@ -32,7 +32,7 @@
content="{% firstof page_description 'ttvdrops - Track Twitch drops.' %}" />
+ content="{% firstof page_url request.build_absolute_uri %}" />
{% if page_image %}
{% if page_image_width and page_image_height %}
@@ -41,8 +41,7 @@
{% endif %}
{% endif %}
{# Twitter Card tags for rich previews #}
-
+
@@ -51,13 +50,20 @@
{% if published_date %}{% endif %}
{% if modified_date %}{% endif %}
{# Canonical tag #}
-
+
{# Pagination links (for crawler efficiency) #}
{% if pagination_info %}
{% for link in pagination_info %}{% endfor %}
{% endif %}
{# Schema.org JSON-LD structured data #}
-{% if schema_data %}{% endif %}
+{% if schema_data %}
+
+{% endif %}
{# Breadcrumb schema #}
-{% if breadcrumb_schema %}{% endif %}
+{% if breadcrumb_schema %}
+
+{% endif %}
diff --git a/twitch/feeds.py b/twitch/feeds.py
index b06c02b..dda8a71 100644
--- a/twitch/feeds.py
+++ b/twitch/feeds.py
@@ -1,13 +1,9 @@
import logging
import re
-from collections.abc import Callable
from typing import TYPE_CHECKING
from typing import Literal
-import django.contrib.syndication.views as syndication_views
-from django.conf import settings
from django.contrib.humanize.templatetags.humanize import naturaltime
-from django.contrib.sites.shortcuts import get_current_site
from django.contrib.syndication.views import Feed
from django.db.models import Prefetch
from django.db.models.query import QuerySet
@@ -20,8 +16,6 @@ from django.utils.html import format_html
from django.utils.html import format_html_join
from django.utils.safestring import SafeText
-from core.base_url import build_absolute_uri
-from core.base_url import get_current_site # noqa: F811
from twitch.models import Channel
from twitch.models import ChatBadge
from twitch.models import DropCampaign
@@ -32,15 +26,11 @@ from twitch.models import TimeBasedDrop
if TYPE_CHECKING:
import datetime
- from collections.abc import Callable
- from django.contrib.sites.models import Site
- from django.contrib.sites.requests import RequestSite
from django.db.models import Model
from django.db.models import QuerySet
from django.http import HttpRequest
from django.http import HttpResponse
- from django.utils.feedgenerator import SyndicationFeed
from django.utils.safestring import SafeString
from twitch.models import DropBenefit
@@ -109,14 +99,14 @@ class TTVDropsBaseFeed(Feed):
"""
if self._request is None:
return url
- return build_absolute_uri(url, request=self._request)
+ return self._request.build_absolute_uri(url)
def _absolute_stylesheet_urls(self, request: HttpRequest) -> list[str]:
"""Return stylesheet URLs as absolute HTTP URLs for browser/file compatibility."""
return [
href
if href.startswith(("http://", "https://"))
- else build_absolute_uri(href, request=request)
+ else request.build_absolute_uri(href)
for href in self.stylesheets
]
@@ -161,41 +151,6 @@ class TTVDropsBaseFeed(Feed):
response.content = content.encode(encoding)
- def get_feed(self, obj: object, request: HttpRequest) -> SyndicationFeed:
- """Use deterministic BASE_URL handling for syndication feed generation.
-
- Returns:
- SyndicationFeed: The feed generator instance with the correct site and URL context for absolute URL generation.
- """
- # TODO(TheLovinator): Refactor to avoid this mess. # noqa: TD003
- try:
- from django.contrib.sites import shortcuts as sites_shortcuts # noqa: I001, PLC0415
- except ImportError:
- sites_shortcuts = None
-
- original_get_current_site: Callable[..., Site | RequestSite] | None = (
- sites_shortcuts.get_current_site if sites_shortcuts else None
- )
- original_is_secure: Callable[[], bool] = request.is_secure
-
- if sites_shortcuts is not None:
- sites_shortcuts.get_current_site = get_current_site
-
- original_syndication_get_current_site: (
- Callable[..., Site | RequestSite] | None
- ) = syndication_views.get_current_site # pyright: ignore[reportAttributeAccessIssue]
- syndication_views.get_current_site = get_current_site # pyright: ignore[reportAttributeAccessIssue]
-
- request.is_secure = lambda: settings.BASE_URL.startswith("https://")
-
- try:
- return super().get_feed(obj, request)
- finally:
- if sites_shortcuts is not None and original_get_current_site is not None:
- sites_shortcuts.get_current_site = original_get_current_site
- syndication_views.get_current_site = original_syndication_get_current_site # pyright: ignore[reportAttributeAccessIssue]
- request.is_secure = original_is_secure
-
def __call__(
self,
request: HttpRequest,
diff --git a/twitch/views.py b/twitch/views.py
index ed1c0c7..a541e29 100644
--- a/twitch/views.py
+++ b/twitch/views.py
@@ -28,7 +28,6 @@ from django.utils import timezone
from django.views.generic import DetailView
from django.views.generic import ListView
-from core.base_url import build_absolute_uri
from twitch.models import Channel
from twitch.models import ChatBadge
from twitch.models import ChatBadgeSet
@@ -100,7 +99,7 @@ def _build_image_object(
return {
"@type": "ImageObject",
- "contentUrl": build_absolute_uri(image_url),
+ "contentUrl": request.build_absolute_uri(image_url),
"creditText": creator_name,
"copyrightNotice": copyright_notice or creator_name,
"creator": creator,
@@ -242,7 +241,7 @@ def _build_pagination_info(
prev_url = f"{base_url}&page={page_obj.previous_page_number()}"
pagination_links.append({
"rel": "prev",
- "url": build_absolute_uri(prev_url),
+ "url": request.build_absolute_uri(prev_url),
})
if page_obj.has_next():
@@ -252,7 +251,7 @@ def _build_pagination_info(
next_url = f"{base_url}&page={page_obj.next_page_number()}"
pagination_links.append({
"rel": "next",
- "url": build_absolute_uri(next_url),
+ "url": request.build_absolute_uri(next_url),
})
return pagination_links or None
@@ -321,7 +320,7 @@ def org_list_view(request: HttpRequest) -> HttpResponse:
"@type": "CollectionPage",
"name": "Twitch Organizations",
"description": "List of Twitch organizations.",
- "url": build_absolute_uri("/organizations/"),
+ "url": request.build_absolute_uri("/organizations/"),
}
seo_context: dict[str, Any] = _build_seo_context(
@@ -364,7 +363,7 @@ def organization_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespon
s: Literal["", "s"] = "" if games_count == 1 else "s"
org_description: str = f"{org_name} has {games_count} game{s}."
- url: str = build_absolute_uri(
+ url: str = request.build_absolute_uri(
reverse("twitch:organization_detail", args=[organization.twitch_id]),
)
organization_node: dict[str, Any] = {
@@ -389,11 +388,11 @@ def organization_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespon
# Breadcrumb schema
breadcrumb_schema: dict[str, Any] = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
- {"name": "Organizations", "url": build_absolute_uri("/organizations/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
+ {"name": "Organizations", "url": request.build_absolute_uri("/organizations/")},
{
"name": org_name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("twitch:organization_detail", args=[organization.twitch_id]),
),
},
@@ -497,14 +496,14 @@ def drop_campaign_list_view(request: HttpRequest) -> HttpResponse: # noqa: PLR0
"@type": "CollectionPage",
"name": title,
"description": description,
- "url": build_absolute_uri(base_url),
+ "url": request.build_absolute_uri(base_url),
}
seo_context: dict[str, Any] = _build_seo_context(
page_title=title,
page_description=description,
seo_meta={
- "page_url": build_absolute_uri(base_url),
+ "page_url": request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},
@@ -637,7 +636,7 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
campaign.image_height if campaign.image_file else None
)
- url: str = build_absolute_uri(
+ url: str = request.build_absolute_uri(
reverse("twitch:campaign_detail", args=[campaign.twitch_id]),
)
@@ -668,7 +667,7 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
else "Twitch"
)
campaign_owner_url: str = (
- build_absolute_uri(
+ request.build_absolute_uri(
reverse("twitch:organization_detail", args=[campaign_owner.twitch_id]),
)
if campaign_owner
@@ -702,17 +701,17 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
campaign.game.display_name or campaign.game.name or campaign.game.twitch_id
)
breadcrumb_schema: dict[str, Any] = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
- {"name": "Games", "url": build_absolute_uri("/games/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
+ {"name": "Games", "url": request.build_absolute_uri("/games/")},
{
"name": game_name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("twitch:game_detail", args=[campaign.game.twitch_id]),
),
},
{
"name": campaign_name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("twitch:campaign_detail", args=[campaign.twitch_id]),
),
},
@@ -822,7 +821,7 @@ class GamesGridView(ListView):
"@type": "CollectionPage",
"name": "Twitch Games",
"description": "Twitch games that had or have Twitch drops.",
- "url": build_absolute_uri("/games/"),
+ "url": self.request.build_absolute_uri("/games/"),
}
seo_context: dict[str, Any] = _build_seo_context(
@@ -978,7 +977,7 @@ class GameDetailView(DetailView):
"@type": "VideoGame",
"name": game_name,
"description": game_description,
- "url": build_absolute_uri(
+ "url": self.request.build_absolute_uri(
reverse("twitch:game_detail", args=[game.twitch_id]),
),
}
@@ -993,7 +992,7 @@ class GameDetailView(DetailView):
else "Twitch"
)
owner_url: str = (
- build_absolute_uri(
+ self.request.build_absolute_uri(
reverse("twitch:organization_detail", args=[preferred_owner.twitch_id]),
)
if preferred_owner
@@ -1015,11 +1014,11 @@ class GameDetailView(DetailView):
# Breadcrumb schema
breadcrumb_schema: dict[str, Any] = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
- {"name": "Games", "url": build_absolute_uri("/games/")},
+ {"name": "Home", "url": self.request.build_absolute_uri("/")},
+ {"name": "Games", "url": self.request.build_absolute_uri("/games/")},
{
"name": game_name,
- "url": build_absolute_uri(
+ "url": self.request.build_absolute_uri(
reverse("twitch:game_detail", args=[game.twitch_id]),
),
},
@@ -1115,12 +1114,12 @@ def dashboard(request: HttpRequest) -> HttpResponse:
"@context": "https://schema.org",
"@type": "WebSite",
"name": "ttvdrops",
- "url": build_absolute_uri("/"),
+ "url": request.build_absolute_uri("/"),
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
- "urlTemplate": build_absolute_uri(
+ "urlTemplate": request.build_absolute_uri(
"/search/?q={search_term_string}",
),
},
@@ -1220,14 +1219,14 @@ def reward_campaign_list_view(request: HttpRequest) -> HttpResponse:
"@type": "CollectionPage",
"name": title,
"description": description,
- "url": build_absolute_uri(base_url),
+ "url": request.build_absolute_uri(base_url),
}
seo_context: dict[str, Any] = _build_seo_context(
page_title=title,
page_description=description,
seo_meta={
- "page_url": build_absolute_uri(base_url),
+ "page_url": request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},
@@ -1276,7 +1275,7 @@ def reward_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRes
else f"{campaign_name}"
)
- reward_url: str = build_absolute_uri(
+ reward_url: str = request.build_absolute_uri(
reverse("twitch:reward_campaign_detail", args=[reward_campaign.twitch_id]),
)
@@ -1317,14 +1316,14 @@ def reward_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRes
# Breadcrumb schema
breadcrumb_schema: dict[str, Any] = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
+ {"name": "Home", "url": request.build_absolute_uri("/")},
{
"name": "Reward Campaigns",
- "url": build_absolute_uri("/reward-campaigns/"),
+ "url": request.build_absolute_uri("/reward-campaigns/"),
},
{
"name": campaign_name,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse(
"twitch:reward_campaign_detail",
args=[reward_campaign.twitch_id],
@@ -1419,14 +1418,14 @@ class ChannelListView(ListView):
"@type": "CollectionPage",
"name": "Twitch Channels",
"description": "List of Twitch channels participating in drop campaigns.",
- "url": build_absolute_uri("/channels/"),
+ "url": self.request.build_absolute_uri("/channels/"),
}
seo_context: dict[str, Any] = _build_seo_context(
page_title="Twitch Channels",
page_description="List of Twitch channels participating in drop campaigns.",
seo_meta={
- "page_url": build_absolute_uri(base_url),
+ "page_url": self.request.build_absolute_uri(base_url),
"pagination_info": pagination_info,
"schema_data": collection_schema,
},
@@ -1543,7 +1542,7 @@ class ChannelDetailView(DetailView):
if total_campaigns > 1:
description += "s"
- channel_url: str = build_absolute_uri(
+ channel_url: str = self.request.build_absolute_uri(
reverse("twitch:channel_detail", args=[channel.twitch_id]),
)
channel_node: dict[str, Any] = {
@@ -1570,11 +1569,11 @@ class ChannelDetailView(DetailView):
# Breadcrumb schema
breadcrumb_schema: dict[str, Any] = _build_breadcrumb_schema([
- {"name": "Home", "url": build_absolute_uri("/")},
- {"name": "Channels", "url": build_absolute_uri("/channels/")},
+ {"name": "Home", "url": self.request.build_absolute_uri("/")},
+ {"name": "Channels", "url": self.request.build_absolute_uri("/channels/")},
{
"name": name,
- "url": build_absolute_uri(
+ "url": self.request.build_absolute_uri(
reverse("twitch:channel_detail", args=[channel.twitch_id]),
),
},
@@ -1639,7 +1638,7 @@ def badge_list_view(request: HttpRequest) -> HttpResponse:
"@type": "CollectionPage",
"name": "Twitch chat badges",
"description": "List of Twitch chat badges awarded through drop campaigns.",
- "url": build_absolute_uri("/badges/"),
+ "url": request.build_absolute_uri("/badges/"),
}
seo_context: dict[str, Any] = _build_seo_context(
@@ -1723,7 +1722,7 @@ def badge_set_detail_view(request: HttpRequest, set_id: str) -> HttpResponse:
"@type": "ItemList",
"name": badge_set_name,
"description": badge_set_description,
- "url": build_absolute_uri(
+ "url": request.build_absolute_uri(
reverse("twitch:badge_set_detail", args=[badge_set.set_id]),
),
}
diff --git a/youtube/views.py b/youtube/views.py
index dd6aa18..5695dba 100644
--- a/youtube/views.py
+++ b/youtube/views.py
@@ -5,8 +5,6 @@ from typing import Any
from django.shortcuts import render
-from core.base_url import build_absolute_uri
-
if TYPE_CHECKING:
from django.http import HttpRequest
from django.http import HttpResponse
@@ -138,7 +136,7 @@ def index(request: HttpRequest) -> HttpResponse:
"@type": "ItemList",
"name": PAGE_TITLE,
"description": PAGE_DESCRIPTION,
- "url": build_absolute_uri(request=request),
+ "url": request.build_absolute_uri(),
"itemListElement": list_items,
}