Refactor GameFeed and GameDetailView to use 'owners' instead of 'owner'; update related tests
All checks were successful
Deploy to Server / deploy (push) Successful in 10s
All checks were successful
Deploy to Server / deploy (push) Successful in 10s
This commit is contained in:
parent
6b936f4cf7
commit
77d9d448d7
4 changed files with 65 additions and 13 deletions
|
|
@ -409,12 +409,14 @@ class GameFeed(Feed):
|
||||||
return super().__call__(request, *args, **kwargs)
|
return super().__call__(request, *args, **kwargs)
|
||||||
|
|
||||||
def items(self) -> list[Game]:
|
def items(self) -> list[Game]:
|
||||||
"""Return the latest games (default 200, or limited by ?limit query param)."""
|
"""Return the latest games (default 20, 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 20
|
||||||
return list(Game.objects.order_by("-added_at")[:limit])
|
return list(
|
||||||
|
Game.objects.prefetch_related("owners").order_by("-added_at")[:limit],
|
||||||
|
)
|
||||||
|
|
||||||
def item_title(self, item: Game) -> SafeText:
|
def item_title(self, item: Game) -> SafeText:
|
||||||
"""Return the game name as the item title (SafeText for RSS)."""
|
"""Return the game name as the item title."""
|
||||||
return SafeText(item.get_game_name)
|
return SafeText(item.get_game_name)
|
||||||
|
|
||||||
def item_description(self, item: Game) -> SafeText:
|
def item_description(self, item: Game) -> SafeText:
|
||||||
|
|
@ -424,7 +426,7 @@ class GameFeed(Feed):
|
||||||
name: str = getattr(item, "name", "")
|
name: str = getattr(item, "name", "")
|
||||||
display_name: str = getattr(item, "display_name", "")
|
display_name: str = getattr(item, "display_name", "")
|
||||||
box_art: str = item.box_art_best_url
|
box_art: str = item.box_art_best_url
|
||||||
owner: Organization | None = getattr(item, "owner", None)
|
owner: Organization | None = item.owners.first()
|
||||||
|
|
||||||
description_parts: list[SafeText] = []
|
description_parts: list[SafeText] = []
|
||||||
|
|
||||||
|
|
@ -438,17 +440,28 @@ class GameFeed(Feed):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Get the full URL for TTVDrops game detail page
|
||||||
|
game_url: str = reverse("twitch:game_detail", args=[twitch_id])
|
||||||
|
rss_feed_url: str = reverse("twitch:game_campaign_feed", args=[twitch_id])
|
||||||
|
twitch_directory_url: str = getattr(item, "twitch_directory_url", "")
|
||||||
if slug:
|
if slug:
|
||||||
description_parts.append(
|
description_parts.append(
|
||||||
SafeText(
|
SafeText(
|
||||||
f"<p><a href='https://www.twitch.tv/directory/game/{slug}'>{game_name} by {game_owner}</a></p>",
|
f"<p>New game has been added to ttvdrops.lovinator.space: {game_name} by {game_owner}\n"
|
||||||
|
f"<a href='{game_url}'>Game Details</a>\n"
|
||||||
|
f"<a href='{twitch_directory_url}'>Twitch</a>\n"
|
||||||
|
f"<a href='{rss_feed_url}'>RSS feed</a>\n</p>",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
description_parts.append(SafeText(f"<p>{game_name} by {game_owner}</p>"))
|
description_parts.append(
|
||||||
|
SafeText(
|
||||||
if twitch_id:
|
f"<p>A new game has been added to ttvdrops.lovinator.space: {game_name} by {game_owner}\n"
|
||||||
description_parts.append(SafeText(f"<small>Twitch ID: {twitch_id}</small>"))
|
f"<a href='{game_url}'>Game Details</a>\n"
|
||||||
|
f"<a href='{twitch_directory_url}'>Twitch</a>\n"
|
||||||
|
f"<a href='{rss_feed_url}'>RSS feed</a>\n</p>",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return SafeText("".join(str(part) for part in description_parts))
|
return SafeText("".join(str(part) for part in description_parts))
|
||||||
|
|
||||||
|
|
@ -480,7 +493,7 @@ class GameFeed(Feed):
|
||||||
|
|
||||||
def item_author_name(self, item: Game) -> str:
|
def item_author_name(self, item: Game) -> str:
|
||||||
"""Return the author name for the game, typically the owner organization name."""
|
"""Return the author name for the game, typically the owner organization name."""
|
||||||
owner: Organization | None = getattr(item, "owner", None)
|
owner: Organization | None = item.owners.first()
|
||||||
if owner and owner.name:
|
if owner and owner.name:
|
||||||
return owner.name
|
return owner.name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,14 @@ class RSSFeedTestCase(TestCase):
|
||||||
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
assert response["Content-Type"] == "application/rss+xml; charset=utf-8"
|
||||||
|
content: str = response.content.decode("utf-8")
|
||||||
|
assert "Test Game by Test Organization" in content
|
||||||
|
|
||||||
|
expected_rss_link: str = reverse(
|
||||||
|
"twitch:game_campaign_feed",
|
||||||
|
args=[self.game.twitch_id],
|
||||||
|
)
|
||||||
|
assert expected_rss_link in content
|
||||||
|
|
||||||
def test_campaign_feed(self) -> None:
|
def test_campaign_feed(self) -> None:
|
||||||
"""Test campaign feed returns 200."""
|
"""Test campaign feed returns 200."""
|
||||||
|
|
@ -392,7 +400,8 @@ def test_game_feed_queries_bounded(
|
||||||
game.owners.add(org)
|
game.owners.add(org)
|
||||||
|
|
||||||
url: str = reverse("twitch:game_feed")
|
url: str = reverse("twitch:game_feed")
|
||||||
with django_assert_num_queries(1, exact=True):
|
# One query for games + one prefetch query for owners.
|
||||||
|
with django_assert_num_queries(2, exact=True):
|
||||||
response: _MonkeyPatchedWSGIResponse = client.get(url)
|
response: _MonkeyPatchedWSGIResponse = client.get(url)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
|
||||||
|
|
@ -930,6 +930,36 @@ class TestChannelListView:
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "game" in response.context
|
assert "game" in response.context
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_game_detail_view_serializes_owners_field(
|
||||||
|
self,
|
||||||
|
client: Client,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Game detail JSON payload should use `owners` (M2M), not stale `owner`."""
|
||||||
|
org: Organization = Organization.objects.create(
|
||||||
|
twitch_id="org-game-detail",
|
||||||
|
name="Org Game Detail",
|
||||||
|
)
|
||||||
|
game: Game = Game.objects.create(
|
||||||
|
twitch_id="g2-owners",
|
||||||
|
name="Game2 Owners",
|
||||||
|
display_name="Game2 Owners",
|
||||||
|
)
|
||||||
|
game.owners.add(org)
|
||||||
|
|
||||||
|
monkeypatch.setattr("twitch.views.format_and_color_json", lambda data: data)
|
||||||
|
|
||||||
|
url: str = reverse("twitch:game_detail", args=[game.twitch_id])
|
||||||
|
response: _MonkeyPatchedWSGIResponse = client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
game_data: dict[str, Any] = response.context["game_data"]
|
||||||
|
fields: dict[str, Any] = game_data["fields"]
|
||||||
|
assert "owners" in fields
|
||||||
|
assert fields["owners"] == [org.pk]
|
||||||
|
assert "owner" not in fields
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_org_list_view(self, client: Client) -> None:
|
def test_org_list_view(self, client: Client) -> None:
|
||||||
"""Test org list view returns 200 and has orgs in context."""
|
"""Test org list view returns 200 and has orgs in context."""
|
||||||
|
|
|
||||||
|
|
@ -1216,7 +1216,7 @@ class GameDetailView(DetailView):
|
||||||
"name",
|
"name",
|
||||||
"display_name",
|
"display_name",
|
||||||
"box_art",
|
"box_art",
|
||||||
"owner",
|
"owners",
|
||||||
"added_at",
|
"added_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue