Refactor URL handling to use BASE_URL across the application and add base_url context processor
All checks were successful
Deploy to Server / deploy (push) Successful in 22s
All checks were successful
Deploy to Server / deploy (push) Successful in 22s
This commit is contained in:
parent
999ab368e2
commit
d4fd35769d
11 changed files with 250 additions and 167 deletions
80
core/base_url.py
Normal file
80
core/base_url.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
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
|
||||
17
core/context_processors.py
Normal file
17
core/context_processors.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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", ""),
|
||||
}
|
||||
25
core/tests/test_base_url.py
Normal file
25
core/tests/test_base_url.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
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"
|
||||
|
|
@ -26,6 +26,7 @@ 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
|
||||
|
|
@ -507,7 +508,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=request.build_absolute_uri(reverse("core:docs_rss")),
|
||||
page_url=build_absolute_uri(reverse("core:docs_rss")),
|
||||
)
|
||||
|
||||
return render(
|
||||
|
|
@ -721,7 +722,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
|
|||
dataset_distributions.append({
|
||||
"@type": "DataDownload",
|
||||
"name": dataset["name"],
|
||||
"contentUrl": request.build_absolute_uri(
|
||||
"contentUrl": build_absolute_uri(
|
||||
reverse("core:dataset_backup_download", args=[download_path]),
|
||||
),
|
||||
"encodingFormat": "application/zstd",
|
||||
|
|
@ -731,9 +732,9 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
|
|||
"@context": "https://schema.org",
|
||||
"@type": "Dataset",
|
||||
"name": "Historical archive of Twitch and Kick drop data",
|
||||
"identifier": request.build_absolute_uri(reverse("core:dataset_backups")),
|
||||
"identifier": build_absolute_uri(reverse("core:dataset_backups")),
|
||||
"temporalCoverage": "2024-07-17/..",
|
||||
"url": request.build_absolute_uri(reverse("core:dataset_backups")),
|
||||
"url": build_absolute_uri(reverse("core:dataset_backups")),
|
||||
"license": "https://creativecommons.org/publicdomain/zero/1.0/",
|
||||
"isAccessibleForFree": True,
|
||||
"description": (
|
||||
|
|
@ -753,7 +754,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse:
|
|||
"includedInDataCatalog": {
|
||||
"@type": "DataCatalog",
|
||||
"name": "ttvdrops.lovinator.space",
|
||||
"url": request.build_absolute_uri(reverse("core:dataset_backups")),
|
||||
"url": build_absolute_uri(reverse("core:dataset_backups")),
|
||||
},
|
||||
}
|
||||
if dataset_distributions:
|
||||
|
|
@ -905,7 +906,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=request.build_absolute_uri(reverse("core:search")),
|
||||
page_url=build_absolute_uri(reverse("core:search")),
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
|
|
@ -1006,12 +1007,12 @@ def dashboard(request: HttpRequest) -> HttpResponse:
|
|||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "ttvdrops",
|
||||
"url": request.build_absolute_uri("/"),
|
||||
"url": build_absolute_uri("/"),
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": {
|
||||
"@type": "EntryPoint",
|
||||
"urlTemplate": request.build_absolute_uri(
|
||||
"urlTemplate": build_absolute_uri(
|
||||
"/search/?q={search_term_string}",
|
||||
),
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue