Add support for Atom feeds
This commit is contained in:
parent
bbdcc80334
commit
6c22559fb5
16 changed files with 293 additions and 0 deletions
|
|
@ -920,3 +920,34 @@ class RewardCampaignFeed(Feed):
|
|||
def item_author_name(self, item: RewardCampaign) -> str:
|
||||
"""Return the author name for the reward campaign."""
|
||||
return item.get_feed_author_name()
|
||||
|
||||
|
||||
# Atom feed variants: reuse existing logic but switch the feed generator to Atom
|
||||
class OrganizationAtomFeed(OrganizationRSSFeed):
|
||||
"""Atom feed for latest organizations (reuses OrganizationRSSFeed)."""
|
||||
|
||||
feed_type = feedgenerator.Atom1Feed
|
||||
|
||||
|
||||
class GameAtomFeed(GameFeed):
|
||||
"""Atom feed for newly added games (reuses GameFeed)."""
|
||||
|
||||
feed_type = feedgenerator.Atom1Feed
|
||||
|
||||
|
||||
class DropCampaignAtomFeed(DropCampaignFeed):
|
||||
"""Atom feed for latest drop campaigns (reuses DropCampaignFeed)."""
|
||||
|
||||
feed_type = feedgenerator.Atom1Feed
|
||||
|
||||
|
||||
class GameCampaignAtomFeed(GameCampaignFeed):
|
||||
"""Atom feed for latest drop campaigns for a specific game (reuses GameCampaignFeed)."""
|
||||
|
||||
feed_type = feedgenerator.Atom1Feed
|
||||
|
||||
|
||||
class RewardCampaignAtomFeed(RewardCampaignFeed):
|
||||
"""Atom feed for latest reward campaigns (reuses RewardCampaignFeed)."""
|
||||
|
||||
feed_type = feedgenerator.Atom1Feed
|
||||
|
|
|
|||
|
|
@ -95,6 +95,32 @@ class RSSFeedTestCase(TestCase):
|
|||
assert 'length="42"' in content
|
||||
assert 'type="image/png"' in content
|
||||
|
||||
def test_organization_atom_feed(self) -> None:
|
||||
"""Test organization Atom feed returns 200 and Atom XML."""
|
||||
url: str = reverse("twitch:organization_feed_atom")
|
||||
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/atom+xml; charset=utf-8"
|
||||
content: str = response.content.decode("utf-8")
|
||||
assert "<feed" in content
|
||||
assert "<entry" in content or "<entry" in content
|
||||
|
||||
def test_game_atom_feed(self) -> None:
|
||||
"""Test game Atom feed returns 200 and contains expected content."""
|
||||
url: str = reverse("twitch:game_feed_atom")
|
||||
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert response["Content-Type"] == "application/atom+xml; charset=utf-8"
|
||||
content: str = response.content.decode("utf-8")
|
||||
assert "Owned by Test Organization." in content
|
||||
expected_atom_link: str = reverse(
|
||||
"twitch:game_campaign_feed",
|
||||
args=[self.game.twitch_id],
|
||||
)
|
||||
assert expected_atom_link in content
|
||||
# Atom should include box art URL somewhere in content
|
||||
assert "https://example.com/box.png" in content
|
||||
|
||||
def test_game_feed_enclosure_helpers(self) -> None:
|
||||
"""Helper methods should return values from model fields."""
|
||||
feed = GameFeed()
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ from typing import TYPE_CHECKING
|
|||
from django.urls import path
|
||||
|
||||
from twitch import views
|
||||
from twitch.feeds import DropCampaignAtomFeed
|
||||
from twitch.feeds import DropCampaignFeed
|
||||
from twitch.feeds import GameAtomFeed
|
||||
from twitch.feeds import GameCampaignAtomFeed
|
||||
from twitch.feeds import GameCampaignFeed
|
||||
from twitch.feeds import GameFeed
|
||||
from twitch.feeds import OrganizationAtomFeed
|
||||
from twitch.feeds import OrganizationRSSFeed
|
||||
from twitch.feeds import RewardCampaignAtomFeed
|
||||
from twitch.feeds import RewardCampaignFeed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -105,4 +110,22 @@ urlpatterns: list[URLPattern] = [
|
|||
RewardCampaignFeed(),
|
||||
name="reward_campaign_feed",
|
||||
),
|
||||
# Atom feeds (added alongside RSS to preserve backward compatibility)
|
||||
path("atom/campaigns/", DropCampaignAtomFeed(), name="campaign_feed_atom"),
|
||||
path("atom/games/", GameAtomFeed(), name="game_feed_atom"),
|
||||
path(
|
||||
"atom/games/<str:twitch_id>/campaigns/",
|
||||
GameCampaignAtomFeed(),
|
||||
name="game_campaign_feed_atom",
|
||||
),
|
||||
path(
|
||||
"atom/organizations/",
|
||||
OrganizationAtomFeed(),
|
||||
name="organization_feed_atom",
|
||||
),
|
||||
path(
|
||||
"atom/reward-campaigns/",
|
||||
RewardCampaignAtomFeed(),
|
||||
name="reward_campaign_feed_atom",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -38,10 +38,15 @@ from pygments import highlight
|
|||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers.data import JsonLexer
|
||||
|
||||
from twitch.feeds import DropCampaignAtomFeed
|
||||
from twitch.feeds import DropCampaignFeed
|
||||
from twitch.feeds import GameAtomFeed
|
||||
from twitch.feeds import GameCampaignAtomFeed
|
||||
from twitch.feeds import GameCampaignFeed
|
||||
from twitch.feeds import GameFeed
|
||||
from twitch.feeds import OrganizationAtomFeed
|
||||
from twitch.feeds import OrganizationRSSFeed
|
||||
from twitch.feeds import RewardCampaignAtomFeed
|
||||
from twitch.feeds import RewardCampaignFeed
|
||||
from twitch.models import Channel
|
||||
from twitch.models import ChatBadge
|
||||
|
|
@ -1808,30 +1813,46 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
|
|||
)
|
||||
return ""
|
||||
|
||||
show_atom: bool = bool(request.GET.get("show_atom"))
|
||||
|
||||
feeds: list[dict[str, str]] = [
|
||||
{
|
||||
"title": "All Organizations",
|
||||
"description": "Latest organizations added to TTVDrops",
|
||||
"url": absolute(reverse("twitch:organization_feed")),
|
||||
"atom_url": absolute(reverse("twitch:organization_feed_atom")),
|
||||
"example_xml": render_feed(OrganizationRSSFeed()),
|
||||
"example_xml_atom": render_feed(OrganizationAtomFeed())
|
||||
if show_atom
|
||||
else "",
|
||||
},
|
||||
{
|
||||
"title": "All Games",
|
||||
"description": "Latest games added to TTVDrops",
|
||||
"url": absolute(reverse("twitch:game_feed")),
|
||||
"atom_url": absolute(reverse("twitch:game_feed_atom")),
|
||||
"example_xml": render_feed(GameFeed()),
|
||||
"example_xml_atom": render_feed(GameAtomFeed()) if show_atom else "",
|
||||
},
|
||||
{
|
||||
"title": "All Drop Campaigns",
|
||||
"description": "Latest drop campaigns across all games",
|
||||
"url": absolute(reverse("twitch:campaign_feed")),
|
||||
"atom_url": absolute(reverse("twitch:campaign_feed_atom")),
|
||||
"example_xml": render_feed(DropCampaignFeed()),
|
||||
"example_xml_atom": render_feed(DropCampaignAtomFeed())
|
||||
if show_atom
|
||||
else "",
|
||||
},
|
||||
{
|
||||
"title": "All Reward Campaigns",
|
||||
"description": "Latest reward campaigns (Quest rewards) on Twitch",
|
||||
"url": absolute(reverse("twitch:reward_campaign_feed")),
|
||||
"atom_url": absolute(reverse("twitch:reward_campaign_feed_atom")),
|
||||
"example_xml": render_feed(RewardCampaignFeed()),
|
||||
"example_xml_atom": render_feed(RewardCampaignAtomFeed())
|
||||
if show_atom
|
||||
else "",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -1851,10 +1872,25 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
|
|||
if sample_game
|
||||
else absolute("/rss/games/<game_id>/campaigns/")
|
||||
),
|
||||
"atom_url": (
|
||||
absolute(
|
||||
reverse(
|
||||
"twitch:game_campaign_feed_atom",
|
||||
args=[sample_game.twitch_id],
|
||||
),
|
||||
)
|
||||
if sample_game
|
||||
else absolute("/atom/games/<game_id>/campaigns/")
|
||||
),
|
||||
"has_sample": bool(sample_game),
|
||||
"example_xml": render_feed(GameCampaignFeed(), sample_game.twitch_id)
|
||||
if sample_game
|
||||
else "",
|
||||
"example_xml_atom": (
|
||||
render_feed(GameCampaignAtomFeed(), sample_game.twitch_id)
|
||||
if sample_game and show_atom
|
||||
else ""
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue