Only show current drops
All checks were successful
Deploy to Server / deploy (push) Successful in 10s

This commit is contained in:
Joakim Hellsén 2026-03-12 00:07:04 +01:00
commit d876b39b08
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
2 changed files with 149 additions and 5 deletions

View file

@ -58,6 +58,28 @@ def _with_campaign_related(queryset: QuerySet[DropCampaign]) -> QuerySet[DropCam
) )
def _active_drop_campaigns(queryset: QuerySet[DropCampaign]) -> QuerySet[DropCampaign]:
"""Filter a campaign queryset down to campaigns active right now.
Returns:
QuerySet[DropCampaign]: Queryset with only active drop campaigns.
"""
now: datetime.datetime = timezone.now()
return queryset.filter(start_at__lte=now, end_at__gte=now)
def _active_reward_campaigns(
queryset: QuerySet[RewardCampaign],
) -> QuerySet[RewardCampaign]:
"""Filter a reward campaign queryset down to campaigns active right now.
Returns:
QuerySet[RewardCampaign]: Queryset with only active reward campaigns.
"""
now: datetime.datetime = timezone.now()
return queryset.filter(starts_at__lte=now, ends_at__gte=now)
def genereate_details_link_html(item: DropCampaign) -> list[SafeText]: def genereate_details_link_html(item: DropCampaign) -> list[SafeText]:
"""Helper method to append a details link to the description if available. """Helper method to append a details link to the description if available.
@ -733,7 +755,9 @@ class DropCampaignFeed(Feed):
def items(self) -> list[DropCampaign]: def items(self) -> list[DropCampaign]:
"""Return the latest drop campaigns ordered by most recent start date (default 200, or limited by ?limit query param).""" """Return the latest drop campaigns ordered by most recent start date (default 200, or limited by ?limit query param)."""
limit: int = self._limit if self._limit is not None else 200 limit: int = self._limit if self._limit is not None else 200
queryset: QuerySet[DropCampaign] = DropCampaign.objects.order_by("-start_at") queryset: QuerySet[DropCampaign] = _active_drop_campaigns(
DropCampaign.objects.order_by("-start_at"),
)
return list(_with_campaign_related(queryset)[:limit]) return list(_with_campaign_related(queryset)[:limit])
def item_title(self, item: DropCampaign) -> SafeText: def item_title(self, item: DropCampaign) -> SafeText:
@ -859,9 +883,11 @@ class GameCampaignFeed(Feed):
def items(self, obj: Game) -> list[DropCampaign]: def items(self, obj: Game) -> list[DropCampaign]:
"""Return the latest drop campaigns for this game, ordered by most recent start date (default 200, or limited by ?limit query param).""" """Return the latest drop campaigns for this game, ordered by most recent start date (default 200, or limited by ?limit query param)."""
limit: int = self._limit if self._limit is not None else 200 limit: int = self._limit if self._limit is not None else 200
queryset: QuerySet[DropCampaign] = DropCampaign.objects.filter( queryset: QuerySet[DropCampaign] = _active_drop_campaigns(
DropCampaign.objects.filter(
game=obj, game=obj,
).order_by("-start_at") ).order_by("-start_at"),
)
return list(_with_campaign_related(queryset)[:limit]) return list(_with_campaign_related(queryset)[:limit])
def item_title(self, item: DropCampaign) -> SafeText: def item_title(self, item: DropCampaign) -> SafeText:
@ -963,8 +989,11 @@ class RewardCampaignFeed(Feed):
def items(self) -> list[RewardCampaign]: def items(self) -> list[RewardCampaign]:
"""Return the latest reward campaigns (default 200, or limited by ?limit query param).""" """Return the latest reward campaigns (default 200, or limited by ?limit query param)."""
limit: int = self._limit if self._limit is not None else 200 limit: int = self._limit if self._limit is not None else 200
queryset: QuerySet[RewardCampaign] = _active_reward_campaigns(
RewardCampaign.objects.select_related("game").order_by("-added_at"),
)
return list( return list(
RewardCampaign.objects.select_related("game").order_by("-added_at")[:limit], queryset[:limit],
) )
def item_title(self, item: RewardCampaign) -> SafeText: def item_title(self, item: RewardCampaign) -> SafeText:

View file

@ -29,6 +29,8 @@ from twitch.models import TimeBasedDrop
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
if TYPE_CHECKING: if TYPE_CHECKING:
import datetime
from django.test.client import _MonkeyPatchedWSGIResponse from django.test.client import _MonkeyPatchedWSGIResponse
from twitch.tests.test_badge_views import Client from twitch.tests.test_badge_views import Client
@ -146,6 +148,35 @@ class RSSFeedTestCase(TestCase):
assert 'length="314"' in content assert 'length="314"' in content
assert 'type="image/gif"' in content assert 'type="image/gif"' in content
def test_campaign_feed_only_includes_active_campaigns(self) -> None:
"""Campaign feed should exclude past and upcoming campaigns."""
now: datetime.datetime = timezone.now()
DropCampaign.objects.create(
twitch_id="past-campaign-123",
name="Past Campaign",
game=self.game,
start_at=now - timedelta(days=10),
end_at=now - timedelta(days=1),
operation_names=["DropCampaignDetails"],
)
DropCampaign.objects.create(
twitch_id="upcoming-campaign-123",
name="Upcoming Campaign",
game=self.game,
start_at=now + timedelta(days=1),
end_at=now + timedelta(days=10),
operation_names=["DropCampaignDetails"],
)
url: str = reverse("twitch:campaign_feed")
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
assert response.status_code == 200
content: str = response.content.decode("utf-8")
assert "Test Campaign" in content
assert "Past Campaign" not in content
assert "Upcoming Campaign" not in content
def test_campaign_feed_enclosure_helpers(self) -> None: def test_campaign_feed_enclosure_helpers(self) -> None:
"""Helper methods for campaigns should respect new fields.""" """Helper methods for campaigns should respect new fields."""
feed = DropCampaignFeed() feed = DropCampaignFeed()
@ -228,6 +259,90 @@ class RSSFeedTestCase(TestCase):
assert 'length="314"' in content assert 'length="314"' in content
assert 'type="image/gif"' in content assert 'type="image/gif"' in content
def test_game_campaign_feed_only_includes_active_campaigns(self) -> None:
"""Game campaign feed should exclude old and upcoming campaigns."""
now: datetime.datetime = timezone.now()
DropCampaign.objects.create(
twitch_id="game-past-campaign-123",
name="Game Past Campaign",
game=self.game,
start_at=now - timedelta(days=10),
end_at=now - timedelta(days=1),
operation_names=["DropCampaignDetails"],
)
DropCampaign.objects.create(
twitch_id="game-upcoming-campaign-123",
name="Game Upcoming Campaign",
game=self.game,
start_at=now + timedelta(days=1),
end_at=now + timedelta(days=10),
operation_names=["DropCampaignDetails"],
)
url: str = reverse("twitch:game_campaign_feed", args=[self.game.twitch_id])
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
assert response.status_code == 200
content: str = response.content.decode("utf-8")
assert "Test Campaign" in content
assert "Game Past Campaign" not in content
assert "Game Upcoming Campaign" not in content
def test_reward_campaign_feed_only_includes_active_campaigns(self) -> None:
"""Reward campaign feed should exclude old and upcoming campaigns."""
now: datetime.datetime = timezone.now()
RewardCampaign.objects.create(
twitch_id="active-reward-123",
name="Active Reward Campaign",
brand="Test Brand",
starts_at=now - timedelta(days=1),
ends_at=now + timedelta(days=1),
status="ACTIVE",
summary="Active reward",
instructions="Do things",
external_url="https://example.com/active-reward",
about_url="https://example.com/about-active-reward",
is_sitewide=False,
game=self.game,
)
RewardCampaign.objects.create(
twitch_id="past-reward-123",
name="Past Reward Campaign",
brand="Test Brand",
starts_at=now - timedelta(days=10),
ends_at=now - timedelta(days=1),
status="EXPIRED",
summary="Past reward",
instructions="Was active",
external_url="https://example.com/past-reward",
about_url="https://example.com/about-past-reward",
is_sitewide=False,
game=self.game,
)
RewardCampaign.objects.create(
twitch_id="upcoming-reward-123",
name="Upcoming Reward Campaign",
brand="Test Brand",
starts_at=now + timedelta(days=1),
ends_at=now + timedelta(days=10),
status="UPCOMING",
summary="Upcoming reward",
instructions="Wait",
external_url="https://example.com/upcoming-reward",
about_url="https://example.com/about-upcoming-reward",
is_sitewide=False,
game=self.game,
)
url: str = reverse("twitch:reward_campaign_feed")
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
assert response.status_code == 200
content: str = response.content.decode("utf-8")
assert "Active Reward Campaign" in content
assert "Past Reward Campaign" not in content
assert "Upcoming Reward Campaign" not in content
def test_game_campaign_feed_enclosure_helpers(self) -> None: def test_game_campaign_feed_enclosure_helpers(self) -> None:
"""GameCampaignFeed helper methods should pull from the model fields.""" """GameCampaignFeed helper methods should pull from the model fields."""
feed = GameCampaignFeed() feed = GameCampaignFeed()