Compare commits
No commits in common. "768d986556a227a9ab3109dec10b8e5c738d184d" and "bbdcc80334d856452c06320ba9493f21dd9a985c" have entirely different histories.
768d986556
...
bbdcc80334
21 changed files with 6 additions and 381 deletions
|
|
@ -103,7 +103,7 @@ MEDIA_URL = "/media/"
|
|||
|
||||
STATIC_ROOT: Path = DATA_DIR / "staticfiles"
|
||||
STATIC_ROOT.mkdir(exist_ok=True)
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_URL = "static/"
|
||||
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<rect width="100%" height="100%" fill="#9146ff" />
|
||||
<g fill="#fff" font-family="Arial, Helvetica, sans-serif" font-size="24" font-weight="700">
|
||||
<text x="50%" y="45%" dominant-baseline="middle" text-anchor="middle">:(</text>
|
||||
<text x="50%" y="60%" dominant-baseline="middle" text-anchor="middle">404</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 457 B |
|
|
@ -23,43 +23,6 @@
|
|||
{% endblock title %}
|
||||
</title>
|
||||
{% include "includes/meta_tags.html" %}
|
||||
<!-- Feed discovery links -->
|
||||
<!-- Read {% url 'twitch:docs_rss' %} for more details on available feeds -->
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="All campaigns (RSS)"
|
||||
href="{% url 'twitch:campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="All campaigns (Atom)"
|
||||
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Newly added games (RSS)"
|
||||
href="{% url 'twitch:game_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Newly added games (Atom)"
|
||||
href="{% url 'twitch:game_feed_atom' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Newly added organizations (RSS)"
|
||||
href="{% url 'twitch:organization_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Newly added organizations (Atom)"
|
||||
href="{% url 'twitch:organization_feed_atom' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Newly added reward campaigns (RSS)"
|
||||
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Newly added reward campaigns (Atom)"
|
||||
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||
{# Allow child templates to inject page-specific alternates into the head #}
|
||||
{% block extra_head %}
|
||||
{% endblock extra_head %}
|
||||
<style>
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
|
|
|
|||
|
|
@ -4,18 +4,6 @@
|
|||
{% block title %}
|
||||
{{ campaign.clean_name }}
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
{% if campaign and campaign.game %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="{{ campaign.game.display_name }} campaigns (RSS)"
|
||||
href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="{{ campaign.game.display_name }} campaigns (Atom)"
|
||||
href="{% url 'twitch:game_campaign_feed_atom' campaign.game.twitch_id %}" />
|
||||
{% endif %}
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<!-- Campaign Title -->
|
||||
<h1>
|
||||
|
|
@ -83,8 +71,6 @@
|
|||
{% if campaign.game %}
|
||||
<a href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}"
|
||||
title="RSS feed for {{ campaign.game.display_name }} campaigns">[rss]</a>
|
||||
<a href="{% url 'twitch:game_campaign_feed_atom' campaign.game.twitch_id %}"
|
||||
title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if allowed_channels %}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,6 @@
|
|||
{% block title %}
|
||||
Drop Campaigns - Twitch Drops Tracker
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="All campaigns (RSS)"
|
||||
href="{% url 'twitch:campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="All campaigns (Atom)"
|
||||
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<main>
|
||||
<header>
|
||||
|
|
@ -23,8 +13,6 @@
|
|||
<div>
|
||||
<a href="{% url 'twitch:campaign_feed' %}"
|
||||
title="RSS feed for all campaigns">[rss]</a>
|
||||
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
||||
title="Atom feed for all campaigns">[atom]</a>
|
||||
<a href="{% url 'twitch:export_campaigns_csv' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
||||
title="Export campaigns as CSV">[csv]</a>
|
||||
<a href="{% url 'twitch:export_campaigns_json' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
||||
|
|
|
|||
|
|
@ -4,16 +4,6 @@
|
|||
{% block title %}
|
||||
Twitch drops
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="All campaigns (RSS)"
|
||||
href="{% url 'twitch:campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="All campaigns (Atom)"
|
||||
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<main>
|
||||
<h1>Twitch Drops</h1>
|
||||
|
|
@ -26,9 +16,6 @@ Hover over the end time to see the exact date and time.
|
|||
<a href="{% url 'twitch:campaign_feed' %}"
|
||||
style="margin-right: 1rem"
|
||||
title="RSS feed for all campaigns">RSS feed for campaigns</a>
|
||||
|
|
||||
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
||||
title="Atom feed for campaigns">Atom feed for campaigns</a>
|
||||
</div>
|
||||
{% if campaigns_by_game %}
|
||||
{% for game_id, game_data in campaigns_by_game.items %}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,6 @@
|
|||
<main>
|
||||
<h1>RSS Feeds Documentation</h1>
|
||||
<p>This page lists all available RSS feeds for TTVDrops.</p>
|
||||
<p>
|
||||
Note: Atom feeds are also available for the same resources under the
|
||||
<code>/atom/</code> endpoints (links labeled "Atom" are shown next to RSS links).
|
||||
Both RSS and Atom formats are supported and served in parallel for backward compatibility.
|
||||
</p>
|
||||
<section>
|
||||
<h2>Global RSS Feeds</h2>
|
||||
<p>These feeds contain all items across the entire site:</p>
|
||||
|
|
@ -22,16 +17,8 @@
|
|||
<p>{{ feed.description }}</p>
|
||||
<p>
|
||||
<a href="{{ feed.url }}">Subscribe to {{ feed.title }} RSS Feed</a>
|
||||
{% if feed.atom_url %}
|
||||
|
|
||||
<a href="{{ feed.atom_url }}">Subscribe to {{ feed.title }} Atom Feed</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
||||
{% if feed.example_xml_atom %}
|
||||
<h4>Atom example</h4>
|
||||
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -48,22 +35,13 @@
|
|||
<p>{{ feed.description }}</p>
|
||||
<p>
|
||||
Endpoint: <code>{{ feed.url }}</code>
|
||||
{% if feed.atom_url %} | Atom: <code>{{ feed.atom_url }}</code>{% endif %}
|
||||
</p>
|
||||
{% if feed.has_sample %}
|
||||
<p>
|
||||
<a href="{{ feed.url }}">View a live example</a>
|
||||
{% if feed.atom_url %}
|
||||
|
|
||||
<a href="{{ feed.atom_url }}">View Atom example</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
||||
{% if feed.example_xml_atom %}
|
||||
<h4>Atom example</h4>
|
||||
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -3,18 +3,6 @@
|
|||
{% block title %}
|
||||
{{ game.display_name }}
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
{% if game %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="{{ game.display_name }} campaigns (RSS)"
|
||||
href="{% url 'twitch:game_campaign_feed' game.twitch_id %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="{{ game.display_name }} campaigns (Atom)"
|
||||
href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}" />
|
||||
{% endif %}
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<!-- Game Title -->
|
||||
<h1>
|
||||
|
|
@ -25,9 +13,6 @@
|
|||
<div>
|
||||
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
|
||||
title="RSS feed for {{ game.display_name }} campaigns">RSS feed for {{ game.display_name }} campaigns</a>
|
||||
|
|
||||
<a href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}"
|
||||
title="Atom feed for {{ game.display_name }} campaigns">Atom feed for {{ game.display_name }} campaigns</a>
|
||||
</div>
|
||||
<!-- Game image -->
|
||||
{% if game.box_art_best_url %}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,6 @@
|
|||
{% block title %}
|
||||
Games - Grid View
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Newly added games (RSS)"
|
||||
href="{% url 'twitch:game_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Newly added games (Atom)"
|
||||
href="{% url 'twitch:game_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<main>
|
||||
<header>
|
||||
|
|
@ -20,8 +10,6 @@
|
|||
<div>
|
||||
<a href="{% url 'twitch:games_list' %}" title="View games as list">[list]</a>
|
||||
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
||||
<a href="{% url 'twitch:game_feed_atom' %}"
|
||||
title="Atom feed for all games">[atom]</a>
|
||||
<a href="{% url 'twitch:export_games_csv' %}"
|
||||
title="Export all games as CSV">[csv]</a>
|
||||
<a href="{% url 'twitch:export_games_json' %}"
|
||||
|
|
|
|||
|
|
@ -2,24 +2,12 @@
|
|||
{% block title %}
|
||||
Games - List View
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Newly added games (RSS)"
|
||||
href="{% url 'twitch:game_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Newly added games (Atom)"
|
||||
href="{% url 'twitch:game_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<main>
|
||||
<h1>Games List</h1>
|
||||
<div>
|
||||
<a href="{% url 'twitch:games_grid' %}" title="View games as grid">[grid]</a>
|
||||
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
||||
<a href="{% url 'twitch:game_feed_atom' %}"
|
||||
title="Atom feed for all games">[atom]</a>
|
||||
<a href="{% url 'twitch:export_games_csv' %}"
|
||||
title="Export all games as CSV">[csv]</a>
|
||||
<a href="{% url 'twitch:export_games_json' %}"
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@
|
|||
<a href="{% url 'twitch:organization_feed' %}"
|
||||
style="margin-right: 1rem"
|
||||
title="RSS feed for all organizations">RSS feed for organizations</a>
|
||||
<a href="{% url 'twitch:organization_feed_atom' %}"
|
||||
title="Atom feed for all organizations">[atom]</a>
|
||||
</div>
|
||||
<!-- Export Options -->
|
||||
<div style="margin-bottom: 1rem; display: flex; gap: 1rem;">
|
||||
|
|
|
|||
|
|
@ -2,20 +2,6 @@
|
|||
{% block title %}
|
||||
{{ organization.name }}
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
{% if games %}
|
||||
{% for game in games %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="{{ game.display_name|default:game.name|default:game.twitch_id }} campaigns (RSS)"
|
||||
href="{% url 'twitch:game_campaign_feed' game.twitch_id %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="{{ game.display_name|default:game.name|default:game.twitch_id }} campaigns (Atom)"
|
||||
href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}" />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<h1 id="org-name">{{ organization.name }}</h1>
|
||||
<theader>
|
||||
|
|
|
|||
|
|
@ -4,16 +4,6 @@
|
|||
{% block title %}
|
||||
{{ reward_campaign.name }}
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Reward campaigns (RSS)"
|
||||
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Reward campaigns (Atom)"
|
||||
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<!-- Campaign Title -->
|
||||
{% if reward_campaign.brand %}
|
||||
|
|
@ -34,8 +24,6 @@
|
|||
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
||||
style="margin-right: 1rem"
|
||||
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
||||
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
||||
title="Atom feed for all reward campaigns">[atom]</a>
|
||||
</div>
|
||||
<!-- Campaign Summary -->
|
||||
{% if reward_campaign.summary %}<p id="campaign-summary">{{ reward_campaign.summary|linebreaksbr }}</p>{% endif %}
|
||||
|
|
|
|||
|
|
@ -3,16 +3,6 @@
|
|||
{% block title %}
|
||||
Reward Campaigns - Twitch Drops Tracker
|
||||
{% endblock title %}
|
||||
{% block extra_head %}
|
||||
<link rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Reward campaigns (RSS)"
|
||||
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||
<link rel="alternate"
|
||||
type="application/atom+xml"
|
||||
title="Reward campaigns (Atom)"
|
||||
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||
{% endblock extra_head %}
|
||||
{% block content %}
|
||||
<h1 id="page-title">Reward Campaigns (Quest Rewards)</h1>
|
||||
<p>Browse all available quest reward campaigns</p>
|
||||
|
|
@ -21,8 +11,6 @@
|
|||
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
||||
style="margin-right: 1rem"
|
||||
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
||||
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
||||
title="Atom feed for all reward campaigns">[atom]</a>
|
||||
</div>
|
||||
<!-- Filter Form -->
|
||||
<form id="filter-form"
|
||||
|
|
|
|||
|
|
@ -1,38 +1,8 @@
|
|||
import io
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.db.models.fields.files import FieldFile
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
class TwitchConfig(AppConfig):
|
||||
"""Django app configuration for the Twitch app."""
|
||||
"""AppConfig subclass for the 'twitch' application."""
|
||||
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "twitch"
|
||||
|
||||
def ready(self) -> None: # noqa: D102
|
||||
logger: logging.Logger = logging.getLogger("ttvdrops.apps")
|
||||
|
||||
# Patch FieldFile.open to swallow FileNotFoundError and provide
|
||||
# an empty in-memory file-like object so image dimension
|
||||
# calculations don't crash when the on-disk file was removed.
|
||||
try:
|
||||
orig_open: Callable[..., FieldFile] = FieldFile.open
|
||||
|
||||
def _safe_open(self: FieldFile, mode: str = "rb") -> FieldFile:
|
||||
try:
|
||||
return orig_open(self, mode)
|
||||
except FileNotFoundError:
|
||||
# Provide an empty BytesIO so subsequent dimension checks
|
||||
# read harmlessly and return (None, None).
|
||||
self._file = io.BytesIO(b"") # pyright: ignore[reportAttributeAccessIssue]
|
||||
return self
|
||||
|
||||
FieldFile.open = _safe_open
|
||||
except (AttributeError, TypeError) as exc:
|
||||
logger.debug("Failed to patch FieldFile.open: %s", exc)
|
||||
|
|
|
|||
|
|
@ -920,34 +920,3 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import logging
|
||||
"""Custom template tags for rendering responsive images with modern formats."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
|
|
@ -12,7 +11,6 @@ if TYPE_CHECKING:
|
|||
from django.utils.safestring import SafeText
|
||||
|
||||
register = template.Library()
|
||||
logger = logging.getLogger("ttvdrops.image_tags")
|
||||
|
||||
|
||||
def get_format_url(image_url: str, fmt: str) -> str:
|
||||
|
|
@ -72,18 +70,6 @@ def picture( # noqa: PLR0913, PLR0917
|
|||
if not src:
|
||||
return SafeString("")
|
||||
|
||||
# If the src points to a local MEDIA file but the underlying file is
|
||||
# missing on disk, replace with a small static fallback to avoid
|
||||
# raising FileNotFoundError during template rendering.
|
||||
try:
|
||||
media_url: str = settings.MEDIA_URL or "/media/"
|
||||
if src.startswith(media_url):
|
||||
name: str = src[len(media_url) :].lstrip("/")
|
||||
if not default_storage.exists(name):
|
||||
src = "/static/404.svg"
|
||||
except (ValueError, OSError) as exc:
|
||||
logger.debug("Error while resolving media file %s: %s", src, exc)
|
||||
|
||||
# For Twitch CDN URLs, skip format conversion and use simple img tag
|
||||
if "static-cdn.jtvnw.net" in src:
|
||||
return format_html(
|
||||
|
|
|
|||
|
|
@ -95,32 +95,6 @@ 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()
|
||||
|
|
|
|||
|
|
@ -1,43 +1,13 @@
|
|||
"""Tests for custom image template tags."""
|
||||
|
||||
from django.template import Context
|
||||
from django.template import Template
|
||||
from django.test import SimpleTestCase
|
||||
from django.test import override_settings
|
||||
from django.utils.safestring import SafeString
|
||||
|
||||
from twitch.models import Game
|
||||
from twitch.templatetags.image_tags import get_format_url
|
||||
from twitch.templatetags.image_tags import picture
|
||||
|
||||
|
||||
@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
|
||||
class ImageTagsTests(SimpleTestCase):
|
||||
"""Tests for image template tags and related functionality."""
|
||||
|
||||
def test_picture_empty_src_returns_empty(self) -> None:
|
||||
"""Test that picture tag with empty src returns empty string."""
|
||||
result = picture("")
|
||||
assert not str(result)
|
||||
|
||||
def test_picture_keeps_external_url(self) -> None:
|
||||
"""Test that picture tag does not modify external URLs and does not attempt format conversion."""
|
||||
src = "https://example.com/images/sample.png"
|
||||
result: SafeString = picture(src, alt="alt", width=16, height=16)
|
||||
rendered = str(result)
|
||||
|
||||
# Should still contain the original external URL
|
||||
assert src in rendered
|
||||
|
||||
def test_model_init_with_missing_image_does_not_raise(self) -> None:
|
||||
"""Test that initializing a model with a missing image file does not raise an error."""
|
||||
# Simulate a Game instance with a missing local image file. The
|
||||
# AppConfig.ready() wrapper should prevent FileNotFoundError during
|
||||
# model initialization.
|
||||
g = Game(twitch_id="test-game", box_art_file="campaigns/images/missing.png")
|
||||
# If initialization reached this point without raising, we consider
|
||||
# the protection successful.
|
||||
assert g.twitch_id == "test-game"
|
||||
|
||||
|
||||
class TestGetFormatUrl:
|
||||
"""Tests for the get_format_url helper function."""
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,10 @@ 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:
|
||||
|
|
@ -110,22 +105,4 @@ 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,15 +38,10 @@ 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
|
||||
|
|
@ -1813,46 +1808,30 @@ 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 "",
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -1872,25 +1851,10 @@ 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