Refactor pagination to support multiple pagination links
This commit is contained in:
parent
7f468bbabe
commit
f004307c9c
4 changed files with 49 additions and 42 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -63,6 +63,7 @@
|
||||||
"rewardcampaign",
|
"rewardcampaign",
|
||||||
"runserver",
|
"runserver",
|
||||||
"sendgrid",
|
"sendgrid",
|
||||||
|
"sitelinks",
|
||||||
"sitewide",
|
"sitewide",
|
||||||
"speculationrules",
|
"speculationrules",
|
||||||
"staticfiles",
|
"staticfiles",
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,7 @@
|
||||||
href="{% firstof page_url request.build_absolute_uri %}" />
|
href="{% firstof page_url request.build_absolute_uri %}" />
|
||||||
{# Pagination links (for crawler efficiency) #}
|
{# Pagination links (for crawler efficiency) #}
|
||||||
{% if pagination_info %}
|
{% if pagination_info %}
|
||||||
{% if pagination_info.rel == "prev" %}
|
{% for link in pagination_info %}<link rel="{{ link.rel }}" href="{{ link.url }}" />{% endfor %}
|
||||||
<link rel="prev" href="{{ pagination_info.url }}" />
|
|
||||||
{% elif pagination_info.rel == "next" %}
|
|
||||||
<link rel="next" href="{{ pagination_info.url }}" />
|
|
||||||
{% elif pagination_info.rel == "first" %}
|
|
||||||
<link rel="first" href="{{ pagination_info.url }}" />
|
|
||||||
{% elif pagination_info.rel == "last" %}
|
|
||||||
<link rel="last" href="{{ pagination_info.url }}" />
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{# Schema.org JSON-LD structured data #}
|
{# Schema.org JSON-LD structured data #}
|
||||||
{% if schema_data %}<script type="application/ld+json">{{ schema_data|safe }}</script>{% endif %}
|
{% if schema_data %}<script type="application/ld+json">{{ schema_data|safe }}</script>{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -938,13 +938,12 @@ class TestSEOHelperFunctions:
|
||||||
paginator: Paginator[int] = Paginator(items, 10)
|
paginator: Paginator[int] = Paginator(items, 10)
|
||||||
page: Page[int] = paginator.get_page(1)
|
page: Page[int] = paginator.get_page(1)
|
||||||
|
|
||||||
info: dict[str, str] | None = _build_pagination_info(request, page, "/campaigns/")
|
info: list[dict[str, str]] | None = _build_pagination_info(request, page, "/campaigns/")
|
||||||
|
|
||||||
assert info is not None
|
assert info is not None
|
||||||
assert "url" in info
|
assert len(info) == 1
|
||||||
assert "rel" in info
|
assert info[0]["rel"] == "next"
|
||||||
assert info["rel"] == "next"
|
assert "page=2" in info[0]["url"]
|
||||||
assert "page=2" in info["url"]
|
|
||||||
|
|
||||||
def test_build_pagination_info_with_prev_page(self) -> None:
|
def test_build_pagination_info_with_prev_page(self) -> None:
|
||||||
"""Test _build_pagination_info extracts prev page URL."""
|
"""Test _build_pagination_info extracts prev page URL."""
|
||||||
|
|
@ -955,13 +954,14 @@ class TestSEOHelperFunctions:
|
||||||
paginator: Paginator[int] = Paginator(items, 10)
|
paginator: Paginator[int] = Paginator(items, 10)
|
||||||
page: Page[int] = paginator.get_page(2)
|
page: Page[int] = paginator.get_page(2)
|
||||||
|
|
||||||
info: dict[str, str] | None = _build_pagination_info(request, page, "/campaigns/")
|
info: list[dict[str, str]] | None = _build_pagination_info(request, page, "/campaigns/")
|
||||||
|
|
||||||
assert info is not None
|
assert info is not None
|
||||||
assert "url" in info
|
assert len(info) == 2
|
||||||
assert "rel" in info
|
assert info[0]["rel"] == "prev"
|
||||||
assert info["rel"] == "prev"
|
assert "page=1" in info[0]["url"]
|
||||||
assert "page=1" in info["url"]
|
assert info[1]["rel"] == "next"
|
||||||
|
assert "page=3" in info[1]["url"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
og_type: str = "website",
|
og_type: str = "website",
|
||||||
schema_data: dict[str, Any] | None = None,
|
schema_data: dict[str, Any] | None = None,
|
||||||
breadcrumb_schema: dict[str, Any] | None = None,
|
breadcrumb_schema: dict[str, Any] | None = None,
|
||||||
pagination_info: dict[str, str] | None = None,
|
pagination_info: list[dict[str, str]] | None = None,
|
||||||
published_date: str | None = None,
|
published_date: str | None = None,
|
||||||
modified_date: str | None = None,
|
modified_date: str | None = None,
|
||||||
robots_directive: str = "index, follow",
|
robots_directive: str = "index, follow",
|
||||||
|
|
@ -112,7 +112,7 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
|
||||||
og_type: OpenGraph type (e.g., "website", "article").
|
og_type: OpenGraph type (e.g., "website", "article").
|
||||||
schema_data: Dict representation of Schema.org JSON-LD data.
|
schema_data: Dict representation of Schema.org JSON-LD data.
|
||||||
breadcrumb_schema: Breadcrumb schema dict for navigation hierarchy.
|
breadcrumb_schema: Breadcrumb schema dict for navigation hierarchy.
|
||||||
pagination_info: Dict with "rel" (prev|next|first|last) and "url".
|
pagination_info: List of dicts with "rel" (prev|next|first|last) and "url".
|
||||||
published_date: ISO 8601 published date (e.g., "2025-01-01T00:00:00Z").
|
published_date: ISO 8601 published date (e.g., "2025-01-01T00:00:00Z").
|
||||||
modified_date: ISO 8601 modified date.
|
modified_date: ISO 8601 modified date.
|
||||||
robots_directive: Robots meta content (e.g., "index, follow" or "noindex").
|
robots_directive: Robots meta content (e.g., "index, follow" or "noindex").
|
||||||
|
|
@ -173,7 +173,7 @@ def _build_pagination_info(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
page_obj: Page,
|
page_obj: Page,
|
||||||
base_url: str,
|
base_url: str,
|
||||||
) -> dict[str, str] | None:
|
) -> list[dict[str, str]] | None:
|
||||||
"""Build pagination link info for rel="next"/"prev" tags.
|
"""Build pagination link info for rel="next"/"prev" tags.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -182,30 +182,30 @@ def _build_pagination_info(
|
||||||
base_url: Base URL for pagination (e.g., "/campaigns/?status=active").
|
base_url: Base URL for pagination (e.g., "/campaigns/?status=active").
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict with rel and url, or None if no prev/next.
|
List of dicts with rel and url, or None if no prev/next.
|
||||||
"""
|
"""
|
||||||
pagination_info: dict[str, str] | None = None
|
pagination_links: list[dict[str, str]] = []
|
||||||
|
|
||||||
|
if page_obj.has_previous():
|
||||||
|
prev_url: str = f"{base_url}?page={page_obj.previous_page_number()}"
|
||||||
|
if "?" in base_url:
|
||||||
|
prev_url = f"{base_url}&page={page_obj.previous_page_number()}"
|
||||||
|
pagination_links.append({
|
||||||
|
"rel": "prev",
|
||||||
|
"url": request.build_absolute_uri(prev_url),
|
||||||
|
})
|
||||||
|
|
||||||
if page_obj.has_next():
|
if page_obj.has_next():
|
||||||
next_url: str = f"{base_url}?page={page_obj.next_page_number()}"
|
next_url: str = f"{base_url}?page={page_obj.next_page_number()}"
|
||||||
if "?" in base_url:
|
if "?" in base_url:
|
||||||
# Preserve existing query params
|
# Preserve existing query params
|
||||||
next_url = f"{base_url}&page={page_obj.next_page_number()}"
|
next_url = f"{base_url}&page={page_obj.next_page_number()}"
|
||||||
pagination_info = {
|
pagination_links.append({
|
||||||
"rel": "next",
|
"rel": "next",
|
||||||
"url": request.build_absolute_uri(next_url),
|
"url": request.build_absolute_uri(next_url),
|
||||||
}
|
})
|
||||||
|
|
||||||
if page_obj.has_previous():
|
return pagination_links or None
|
||||||
prev_url: str = f"{base_url}?page={page_obj.previous_page_number()}"
|
|
||||||
if "?" in base_url:
|
|
||||||
prev_url = f"{base_url}&page={page_obj.previous_page_number()}"
|
|
||||||
pagination_info = {
|
|
||||||
"rel": "prev",
|
|
||||||
"url": request.build_absolute_uri(prev_url),
|
|
||||||
}
|
|
||||||
|
|
||||||
return pagination_info
|
|
||||||
|
|
||||||
|
|
||||||
def emote_gallery_view(request: HttpRequest) -> HttpResponse:
|
def emote_gallery_view(request: HttpRequest) -> HttpResponse:
|
||||||
|
|
@ -522,7 +522,7 @@ def drop_campaign_list_view(request: HttpRequest) -> HttpResponse: # noqa: PLR0
|
||||||
elif game_filter:
|
elif game_filter:
|
||||||
base_url += f"?game={game_filter}"
|
base_url += f"?game={game_filter}"
|
||||||
|
|
||||||
pagination_info: dict[str, str] | None = _build_pagination_info(request, campaigns, base_url)
|
pagination_info: list[dict[str, str]] | None = _build_pagination_info(request, campaigns, base_url)
|
||||||
|
|
||||||
# CollectionPage schema for campaign list
|
# CollectionPage schema for campaign list
|
||||||
collection_schema: dict[str, str] = {
|
collection_schema: dict[str, str] = {
|
||||||
|
|
@ -1062,11 +1062,25 @@ class GameDetailView(DetailView):
|
||||||
campaign__game=game,
|
campaign__game=game,
|
||||||
).prefetch_related("benefits")
|
).prefetch_related("benefits")
|
||||||
|
|
||||||
for drop in drops:
|
# Materialize drops so we can iterate multiple times without extra DB hits
|
||||||
|
drops_list: list[TimeBasedDrop] = list(drops)
|
||||||
|
|
||||||
|
# Collect all benefit names that award badges
|
||||||
|
benefit_badge_titles: set[str] = set()
|
||||||
|
for drop in drops_list:
|
||||||
|
for benefit in drop.benefits.all():
|
||||||
|
if benefit.distribution_type == "BADGE" and benefit.name:
|
||||||
|
benefit_badge_titles.add(benefit.name)
|
||||||
|
|
||||||
|
# Bulk-load all matching ChatBadge instances to avoid N+1 queries
|
||||||
|
badges_by_title: dict[str, ChatBadge] = {
|
||||||
|
badge.title: badge for badge in ChatBadge.objects.filter(title__in=benefit_badge_titles)
|
||||||
|
}
|
||||||
|
|
||||||
|
for drop in drops_list:
|
||||||
for benefit in drop.benefits.all():
|
for benefit in drop.benefits.all():
|
||||||
if benefit.distribution_type == "BADGE":
|
if benefit.distribution_type == "BADGE":
|
||||||
# Find badge by title
|
badge: ChatBadge | None = badges_by_title.get(benefit.name)
|
||||||
badge: ChatBadge | None = ChatBadge.objects.filter(title=benefit.name).first()
|
|
||||||
if badge:
|
if badge:
|
||||||
drop_awarded_badges[drop.twitch_id] = badge
|
drop_awarded_badges[drop.twitch_id] = badge
|
||||||
|
|
||||||
|
|
@ -1360,7 +1374,7 @@ def reward_campaign_list_view(request: HttpRequest) -> HttpResponse:
|
||||||
elif game_filter:
|
elif game_filter:
|
||||||
base_url += f"?game={game_filter}"
|
base_url += f"?game={game_filter}"
|
||||||
|
|
||||||
pagination_info: dict[str, str] | None = _build_pagination_info(request, reward_campaigns, base_url)
|
pagination_info: list[dict[str, str]] | None = _build_pagination_info(request, reward_campaigns, base_url)
|
||||||
|
|
||||||
# CollectionPage schema for reward campaigns list
|
# CollectionPage schema for reward campaigns list
|
||||||
collection_schema: dict[str, str | dict[str, str | dict[str, str]]] = {
|
collection_schema: dict[str, str | dict[str, str | dict[str, str]]] = {
|
||||||
|
|
@ -1800,7 +1814,7 @@ class ChannelListView(ListView):
|
||||||
base_url += f"?search={search_query}"
|
base_url += f"?search={search_query}"
|
||||||
|
|
||||||
page_obj: Page | None = context.get("page_obj")
|
page_obj: Page | None = context.get("page_obj")
|
||||||
pagination_info: dict[str, str] | None = (
|
pagination_info: list[dict[str, str]] | None = (
|
||||||
_build_pagination_info(self.request, page_obj, base_url) if isinstance(page_obj, Page) else None
|
_build_pagination_info(self.request, page_obj, base_url) if isinstance(page_obj, Page) else None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue