Add RSS feed links in templates
This commit is contained in:
parent
da923f82da
commit
6a62eaa885
13 changed files with 349 additions and 39 deletions
|
|
@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
|||
|
||||
from django.db.models import Model
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
# MARK: /rss/organizations/
|
||||
|
|
@ -239,3 +240,73 @@ class DropCampaignFeed(Feed):
|
|||
def item_enclosure_mime_type(self, item: DropCampaign) -> str: # noqa: ARG002
|
||||
"""Returns the MIME type of the enclosure."""
|
||||
return "image/jpeg"
|
||||
|
||||
|
||||
# MARK: /rss/games/<twitch_id>/campaigns/
|
||||
class GameCampaignFeed(DropCampaignFeed):
|
||||
"""RSS feed for campaigns of a specific game."""
|
||||
|
||||
def get_object(self, request: HttpRequest, twitch_id: str) -> Game: # noqa: ARG002
|
||||
"""Get the game object for this feed.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
twitch_id: The Twitch ID of the game.
|
||||
|
||||
Returns:
|
||||
Game: The game object.
|
||||
"""
|
||||
return Game.objects.get(twitch_id=twitch_id)
|
||||
|
||||
def title(self, obj: Game) -> str:
|
||||
"""Return the feed title."""
|
||||
return f"TTVDrops: {obj.display_name} Campaigns"
|
||||
|
||||
def link(self, obj: Game) -> str:
|
||||
"""Return the link to the game detail."""
|
||||
return reverse("twitch:game_detail", args=[obj.twitch_id])
|
||||
|
||||
def description(self, obj: Game) -> str:
|
||||
"""Return the feed description."""
|
||||
return f"Latest drop campaigns for {obj.display_name}"
|
||||
|
||||
def items(self, obj: Game) -> list[DropCampaign]:
|
||||
"""Return the latest 100 campaigns for this game."""
|
||||
return list(
|
||||
DropCampaign.objects.filter(game=obj).select_related("game").order_by("-added_at")[:100],
|
||||
)
|
||||
|
||||
|
||||
# MARK: /rss/organizations/<twitch_id>/campaigns/
|
||||
class OrganizationCampaignFeed(DropCampaignFeed):
|
||||
"""RSS feed for campaigns of a specific organization."""
|
||||
|
||||
def get_object(self, request: HttpRequest, twitch_id: str) -> Organization: # noqa: ARG002
|
||||
"""Get the organization object for this feed.
|
||||
|
||||
Args:
|
||||
request: The HTTP request.
|
||||
twitch_id: The Twitch ID of the organization.
|
||||
|
||||
Returns:
|
||||
Organization: The organization object.
|
||||
"""
|
||||
return Organization.objects.get(twitch_id=twitch_id)
|
||||
|
||||
def title(self, obj: Organization) -> str:
|
||||
"""Return the feed title."""
|
||||
return f"TTVDrops: {obj.name} Campaigns"
|
||||
|
||||
def link(self, obj: Organization) -> str:
|
||||
"""Return the link to the organization detail."""
|
||||
return reverse("twitch:organization_detail", args=[obj.twitch_id])
|
||||
|
||||
def description(self, obj: Organization) -> str:
|
||||
"""Return the feed description."""
|
||||
return f"Latest drop campaigns for {obj.name}"
|
||||
|
||||
def items(self, obj: Organization) -> list[DropCampaign]:
|
||||
"""Return the latest 100 campaigns for this organization."""
|
||||
return list(
|
||||
DropCampaign.objects.filter(game__owner=obj).select_related("game").order_by("-added_at")[:100],
|
||||
)
|
||||
|
|
|
|||
139
twitch/tests/test_feeds.py
Normal file
139
twitch/tests/test_feeds.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
"""Test RSS feeds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
||||
from twitch.models import DropCampaign
|
||||
from twitch.models import Game
|
||||
from twitch.models import Organization
|
||||
|
||||
|
||||
class RSSFeedTestCase(TestCase):
|
||||
"""Test RSS feeds."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
"""Set up test fixtures."""
|
||||
self.org = Organization.objects.create(
|
||||
twitch_id="test-org-123",
|
||||
name="Test Organization",
|
||||
)
|
||||
self.game = Game.objects.create(
|
||||
twitch_id="test-game-123",
|
||||
slug="test-game",
|
||||
name="Test Game",
|
||||
display_name="Test Game",
|
||||
owner=self.org,
|
||||
)
|
||||
self.campaign = DropCampaign.objects.create(
|
||||
twitch_id="test-campaign-123",
|
||||
name="Test Campaign",
|
||||
game=self.game,
|
||||
start_at=timezone.now(),
|
||||
end_at=timezone.now() + timedelta(days=7),
|
||||
)
|
||||
|
||||
def test_organization_feed(self) -> None:
|
||||
"""Test organization feed returns 200."""
|
||||
url = reverse("twitch:organization_feed")
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||
|
||||
def test_game_feed(self) -> None:
|
||||
"""Test game feed returns 200."""
|
||||
url = reverse("twitch:game_feed")
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||
|
||||
def test_campaign_feed(self) -> None:
|
||||
"""Test campaign feed returns 200."""
|
||||
url = reverse("twitch:campaign_feed")
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||
|
||||
def test_game_campaign_feed(self) -> None:
|
||||
"""Test game-specific campaign feed returns 200."""
|
||||
url = reverse("twitch:game_campaign_feed", args=[self.game.twitch_id])
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||
# Verify the game name is in the feed
|
||||
content = response.content.decode("utf-8")
|
||||
assert "Test Game" in content
|
||||
|
||||
def test_organization_campaign_feed(self) -> None:
|
||||
"""Test organization-specific campaign feed returns 200."""
|
||||
url = reverse("twitch:organization_campaign_feed", args=[self.org.twitch_id])
|
||||
response = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||
# Verify the organization name is in the feed
|
||||
content = response.content.decode("utf-8")
|
||||
assert "Test Organization" in content
|
||||
|
||||
def test_game_campaign_feed_filters_correctly(self) -> None:
|
||||
"""Test game campaign feed only shows campaigns for that game."""
|
||||
# Create another game with a campaign
|
||||
other_game = Game.objects.create(
|
||||
twitch_id="other-game-123",
|
||||
slug="other-game",
|
||||
name="Other Game",
|
||||
display_name="Other Game",
|
||||
owner=self.org,
|
||||
)
|
||||
DropCampaign.objects.create(
|
||||
twitch_id="other-campaign-123",
|
||||
name="Other Campaign",
|
||||
game=other_game,
|
||||
start_at=timezone.now(),
|
||||
end_at=timezone.now() + timedelta(days=7),
|
||||
)
|
||||
|
||||
# Get feed for first game
|
||||
url = reverse("twitch:game_campaign_feed", args=[self.game.twitch_id])
|
||||
response = self.client.get(url)
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
# Should contain first campaign
|
||||
assert "Test Campaign" in content
|
||||
# Should NOT contain other campaign
|
||||
assert "Other Campaign" not in content
|
||||
|
||||
def test_organization_campaign_feed_filters_correctly(self) -> None:
|
||||
"""Test organization campaign feed only shows campaigns for that organization."""
|
||||
# Create another organization with a game and campaign
|
||||
other_org = Organization.objects.create(
|
||||
twitch_id="other-org-123",
|
||||
name="Other Organization",
|
||||
)
|
||||
other_game = Game.objects.create(
|
||||
twitch_id="other-game-456",
|
||||
slug="other-game-2",
|
||||
name="Other Game 2",
|
||||
display_name="Other Game 2",
|
||||
owner=other_org,
|
||||
)
|
||||
DropCampaign.objects.create(
|
||||
twitch_id="other-campaign-456",
|
||||
name="Other Campaign 2",
|
||||
game=other_game,
|
||||
start_at=timezone.now(),
|
||||
end_at=timezone.now() + timedelta(days=7),
|
||||
)
|
||||
|
||||
# Get feed for first organization
|
||||
url = reverse("twitch:organization_campaign_feed", args=[self.org.twitch_id])
|
||||
response = self.client.get(url)
|
||||
content = response.content.decode("utf-8")
|
||||
|
||||
# Should contain first campaign
|
||||
assert "Test Campaign" in content
|
||||
# Should NOT contain other campaign
|
||||
assert "Other Campaign 2" not in content
|
||||
|
|
@ -6,7 +6,9 @@ from django.urls import path
|
|||
|
||||
from twitch import views
|
||||
from twitch.feeds import DropCampaignFeed
|
||||
from twitch.feeds import GameCampaignFeed
|
||||
from twitch.feeds import GameFeed
|
||||
from twitch.feeds import OrganizationCampaignFeed
|
||||
from twitch.feeds import OrganizationFeed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -28,7 +30,9 @@ urlpatterns: list[URLPattern] = [
|
|||
path("channels/", views.ChannelListView.as_view(), name="channel_list"),
|
||||
path("channels/<str:twitch_id>/", views.ChannelDetailView.as_view(), name="channel_detail"),
|
||||
path("rss/organizations/", OrganizationFeed(), name="organization_feed"),
|
||||
path("rss/organizations/<str:twitch_id>/campaigns/", OrganizationCampaignFeed(), name="organization_campaign_feed"),
|
||||
path("rss/games/", GameFeed(), name="game_feed"),
|
||||
path("rss/games/<str:twitch_id>/campaigns/", GameCampaignFeed(), name="game_campaign_feed"),
|
||||
path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"),
|
||||
path("docs/rss/", views.docs_rss_view, name="docs_rss"),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -792,22 +792,35 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
|
|||
"""
|
||||
feeds: list[dict[str, str]] = [
|
||||
{
|
||||
"title": "Organizations",
|
||||
"description": "Latest organizations",
|
||||
"title": "All Organizations",
|
||||
"description": "Latest organizations added to TTVDrops",
|
||||
"url": "/rss/organizations/",
|
||||
},
|
||||
{
|
||||
"title": "Games",
|
||||
"description": "Latest games",
|
||||
"title": "All Games",
|
||||
"description": "Latest games added to TTVDrops",
|
||||
"url": "/rss/games/",
|
||||
},
|
||||
{
|
||||
"title": "Drop Campaigns",
|
||||
"description": "Latest drop campaigns",
|
||||
"title": "All Drop Campaigns",
|
||||
"description": "Latest drop campaigns across all games",
|
||||
"url": "/rss/campaigns/",
|
||||
},
|
||||
]
|
||||
return render(request, "twitch/docs_rss.html", {"feeds": feeds})
|
||||
|
||||
# Get sample game and organization for examples
|
||||
sample_game = Game.objects.first()
|
||||
sample_org = Organization.objects.first()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"twitch/docs_rss.html",
|
||||
{
|
||||
"feeds": feeds,
|
||||
"sample_game": sample_game,
|
||||
"sample_org": sample_org,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# MARK: /channels/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue