Add DropCampaign image fallback logic and update templates for best image URL
This commit is contained in:
parent
55c2273e27
commit
7f468bbabe
8 changed files with 438 additions and 17 deletions
|
|
@ -446,7 +446,13 @@ class DropCampaign(auto_prefetch.Model):
|
|||
|
||||
@property
|
||||
def image_best_url(self) -> str:
|
||||
"""Return the best URL for the campaign image (local first)."""
|
||||
"""Return the best URL for the campaign image.
|
||||
|
||||
Priority:
|
||||
1. Local cached image file
|
||||
2. Campaign image URL
|
||||
3. First benefit image URL (if campaign has no image)
|
||||
"""
|
||||
try:
|
||||
if self.image_file and getattr(self.image_file, "url", None):
|
||||
return self.image_file.url
|
||||
|
|
@ -455,7 +461,18 @@ class DropCampaign(auto_prefetch.Model):
|
|||
"Failed to resolve DropCampaign.image_file url: %s",
|
||||
exc,
|
||||
)
|
||||
return self.image_url or ""
|
||||
|
||||
if self.image_url:
|
||||
return self.image_url
|
||||
|
||||
# If no campaign image, use the first benefit image
|
||||
for drop in self.time_based_drops.all(): # pyright: ignore[reportAttributeAccessIssue]
|
||||
for benefit in drop.benefits.all(): # pyright: ignore[reportAttributeAccessIssue]
|
||||
benefit_image_url: str = benefit.image_best_url
|
||||
if benefit_image_url:
|
||||
return benefit_image_url
|
||||
|
||||
return ""
|
||||
|
||||
@property
|
||||
def duration_iso(self) -> str:
|
||||
|
|
@ -537,7 +554,7 @@ class DropCampaign(auto_prefetch.Model):
|
|||
|
||||
def get_feed_enclosure_url(self) -> str:
|
||||
"""Return the campaign image URL for RSS enclosures."""
|
||||
return self.image_url
|
||||
return self.image_best_url
|
||||
|
||||
|
||||
# MARK: DropBenefit
|
||||
|
|
|
|||
|
|
@ -1322,3 +1322,123 @@ class TestSEOPaginationLinks:
|
|||
# Should be a dict with rel and url
|
||||
assert isinstance(pagination_info, dict)
|
||||
assert "rel" in pagination_info or pagination_info is None
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestDropCampaignImageFallback:
|
||||
"""Tests for DropCampaign image_best_url property with benefit fallback."""
|
||||
|
||||
def test_image_best_url_returns_campaign_image_url(self) -> None:
|
||||
"""Test that image_best_url returns campaign image_url when present."""
|
||||
game: Game = Game.objects.create(
|
||||
twitch_id="game1",
|
||||
name="test_game",
|
||||
display_name="Test Game",
|
||||
)
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
game=game,
|
||||
image_url="https://example.com/campaign.png",
|
||||
)
|
||||
assert campaign.image_best_url == "https://example.com/campaign.png"
|
||||
|
||||
def test_image_best_url_uses_benefit_image_when_campaign_has_no_image(self) -> None:
|
||||
"""Test that image_best_url returns first benefit image when campaign has no image."""
|
||||
game: Game = Game.objects.create(
|
||||
twitch_id="game1",
|
||||
name="test_game",
|
||||
display_name="Test Game",
|
||||
)
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
game=game,
|
||||
image_url="", # No campaign image
|
||||
)
|
||||
benefit: DropBenefit = DropBenefit.objects.create(
|
||||
twitch_id="benefit1",
|
||||
name="Test Benefit",
|
||||
image_asset_url="https://example.com/benefit.png",
|
||||
)
|
||||
drop: TimeBasedDrop = TimeBasedDrop.objects.create(
|
||||
twitch_id="drop1",
|
||||
name="Test Drop",
|
||||
campaign=campaign,
|
||||
)
|
||||
drop.benefits.add(benefit)
|
||||
|
||||
assert campaign.image_best_url == "https://example.com/benefit.png"
|
||||
|
||||
def test_image_best_url_prefers_campaign_image_over_benefit_image(self) -> None:
|
||||
"""Test that campaign image is preferred over benefit image."""
|
||||
game: Game = Game.objects.create(
|
||||
twitch_id="game1",
|
||||
name="test_game",
|
||||
display_name="Test Game",
|
||||
)
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
game=game,
|
||||
image_url="https://example.com/campaign.png", # Campaign has image
|
||||
)
|
||||
benefit: DropBenefit = DropBenefit.objects.create(
|
||||
twitch_id="benefit1",
|
||||
name="Test Benefit",
|
||||
image_asset_url="https://example.com/benefit.png",
|
||||
)
|
||||
drop: TimeBasedDrop = TimeBasedDrop.objects.create(
|
||||
twitch_id="drop1",
|
||||
name="Test Drop",
|
||||
campaign=campaign,
|
||||
)
|
||||
drop.benefits.add(benefit)
|
||||
|
||||
# Should return campaign image, not benefit image
|
||||
assert campaign.image_best_url == "https://example.com/campaign.png"
|
||||
|
||||
def test_image_best_url_returns_empty_when_no_images(self) -> None:
|
||||
"""Test that image_best_url returns empty string when no images available."""
|
||||
game: Game = Game.objects.create(
|
||||
twitch_id="game1",
|
||||
name="test_game",
|
||||
display_name="Test Game",
|
||||
)
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
game=game,
|
||||
image_url="", # No campaign image
|
||||
)
|
||||
# No benefits or drops
|
||||
|
||||
assert not campaign.image_best_url
|
||||
|
||||
def test_image_best_url_uses_benefit_best_url(self) -> None:
|
||||
"""Test that benefit's image_best_url property is used (prefers local file)."""
|
||||
game: Game = Game.objects.create(
|
||||
twitch_id="game1",
|
||||
name="test_game",
|
||||
display_name="Test Game",
|
||||
)
|
||||
campaign: DropCampaign = DropCampaign.objects.create(
|
||||
twitch_id="camp1",
|
||||
name="Test Campaign",
|
||||
game=game,
|
||||
image_url="", # No campaign image
|
||||
)
|
||||
benefit: DropBenefit = DropBenefit.objects.create(
|
||||
twitch_id="benefit1",
|
||||
name="Test Benefit",
|
||||
image_asset_url="https://example.com/benefit.png",
|
||||
)
|
||||
drop: TimeBasedDrop = TimeBasedDrop.objects.create(
|
||||
twitch_id="drop1",
|
||||
name="Test Drop",
|
||||
campaign=campaign,
|
||||
)
|
||||
drop.benefits.add(benefit)
|
||||
|
||||
# Should use benefit's image_asset_url (since no local file)
|
||||
assert campaign.image_best_url == benefit.image_best_url
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from django.core.paginator import PageNotAnInteger
|
|||
from django.core.paginator import Paginator
|
||||
from django.core.serializers import serialize
|
||||
from django.db.models import Count
|
||||
from django.db.models import Exists
|
||||
from django.db.models import F
|
||||
from django.db.models import OuterRef
|
||||
from django.db.models import Prefetch
|
||||
|
|
@ -856,7 +857,7 @@ def drop_campaign_detail_view(request: HttpRequest, twitch_id: str) -> HttpRespo
|
|||
if campaign.description
|
||||
else f"Twitch drop campaign: {campaign_name}"
|
||||
)
|
||||
campaign_image: str | None = campaign.image_url
|
||||
campaign_image: str | None = campaign.image_best_url
|
||||
|
||||
campaign_schema: dict[str, str | dict[str, str]] = {
|
||||
"@context": "https://schema.org",
|
||||
|
|
@ -1510,10 +1511,21 @@ def debug_view(request: HttpRequest) -> HttpResponse:
|
|||
owners__isnull=True,
|
||||
).order_by("display_name")
|
||||
|
||||
# Campaigns with missing or obviously broken images
|
||||
broken_image_campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter(
|
||||
Q(image_url__isnull=True) | Q(image_url__exact="") | ~Q(image_url__startswith="http"),
|
||||
).select_related("game")
|
||||
# Campaigns with no images at all (no direct URL and no benefit image fallbacks)
|
||||
broken_image_campaigns: QuerySet[DropCampaign] = (
|
||||
DropCampaign.objects
|
||||
.filter(
|
||||
Q(image_url__isnull=True) | Q(image_url__exact="") | ~Q(image_url__startswith="http"),
|
||||
)
|
||||
.exclude(
|
||||
Exists(
|
||||
TimeBasedDrop.objects.filter(campaign=OuterRef("pk")).filter(
|
||||
benefits__image_asset_url__startswith="http",
|
||||
),
|
||||
),
|
||||
)
|
||||
.select_related("game")
|
||||
)
|
||||
|
||||
# Benefits with missing images
|
||||
broken_benefit_images: QuerySet[DropBenefit] = DropBenefit.objects.annotate(
|
||||
|
|
@ -1544,13 +1556,20 @@ def debug_view(request: HttpRequest) -> HttpResponse:
|
|||
.order_by("game__display_name", "name")
|
||||
)
|
||||
|
||||
# Campaigns currently active but image missing
|
||||
# Active campaigns with no images at all (no direct URL and no benefit image fallbacks)
|
||||
active_missing_image: QuerySet[DropCampaign] = (
|
||||
DropCampaign.objects
|
||||
.filter(start_at__lte=now, end_at__gte=now)
|
||||
.filter(
|
||||
Q(image_url__isnull=True) | Q(image_url__exact="") | ~Q(image_url__startswith="http"),
|
||||
)
|
||||
.exclude(
|
||||
Exists(
|
||||
TimeBasedDrop.objects.filter(campaign=OuterRef("pk")).filter(
|
||||
benefits__image_asset_url__startswith="http",
|
||||
),
|
||||
),
|
||||
)
|
||||
.select_related("game")
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue