Skip to content

Commit 97624b5

Browse files
committed
feat: add rss feed for latest downloads
1 parent 097df08 commit 97624b5

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

downloads/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
path('release/<slug:release_slug>/', views.DownloadReleaseDetail.as_view(), name='download_release_detail'),
1010
path('<slug:slug>/', views.DownloadOSList.as_view(), name='download_os_list'),
1111
path('', views.DownloadHome.as_view(), name='download'),
12+
path("feed.rss", views.ReleaseFeed(), name="feed"),
1213
]

downloads/views.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1+
import logging
2+
from typing import Any
3+
4+
import requests
5+
from datetime import datetime
6+
7+
from django.contrib.sites.shortcuts import get_current_site
8+
from django.core.handlers.wsgi import WSGIRequest
19
from django.db.models import Prefetch
210
from django.urls import reverse
311
from django.views.generic import DetailView, TemplateView, ListView, RedirectView
412
from django.http import Http404
13+
from django.contrib.syndication.views import Feed
14+
from django.utils.feedgenerator import Rss201rev2Feed
15+
import pytz
516

617
from .models import OS, Release, ReleaseFile
718

19+
logger = logging.getLogger(__name__)
820

921
class DownloadLatestPython2(RedirectView):
1022
""" Redirect to latest Python 2 release """
@@ -147,3 +159,92 @@ def get_context_data(self, **kwargs):
147159
)
148160

149161
return context
162+
163+
164+
class ReleaseFeed(Feed):
165+
"""Generate an RSS feed of the latest Python releases.
166+
167+
.. note:: It may seem like these are unused methods, but the superclass uses them
168+
using Django's Syndication framework.
169+
Docs: https://docs.djangoproject.com/en/4.2/ref/contrib/syndication/
170+
"""
171+
172+
feed_type = Rss201rev2Feed
173+
title = "Python Releases"
174+
description = "Latest Python releases from Python.org"
175+
176+
@staticmethod
177+
def link() -> str:
178+
"""Return the URL to the main downloads page."""
179+
return reverse('downloads:download')
180+
181+
def get_feed(self, obj: Any, request: WSGIRequest) -> Feed:
182+
"""Store the request object for later use."""
183+
self.request = request
184+
return super().get_feed(obj, request)
185+
186+
def items(self) -> list[dict[str, Any]]:
187+
"""Return the latest Python releases."""
188+
url = self.create_url("/api/v2/downloads/release/")
189+
logger.info(f"Fetching releases from: {url}")
190+
try:
191+
return self._fetch_releases(url)
192+
except requests.RequestException as e:
193+
logger.error(f"Error fetching releases from API: {str(e)}")
194+
except ValueError as e:
195+
logger.error(f"Error parsing JSON from API response: {str(e)}")
196+
except Exception as e:
197+
logger.error(f"Unexpected error in items method: {str(e)}")
198+
return []
199+
200+
@staticmethod
201+
def _fetch_releases(url: str) -> list[dict[str, Any]]:
202+
"""Grabs the latest Python releases from API.
203+
204+
205+
"""
206+
response = requests.get(url, timeout=10)
207+
response.raise_for_status()
208+
data = response.json()
209+
210+
sorted_releases = sorted(data, key=lambda x: x["release_date"], reverse=True)
211+
return sorted_releases[:10]
212+
213+
def item_title(self, item: dict[str, Any]) -> str:
214+
"""Return the release name as the item title."""
215+
return item.get("name", "Unknown Release")
216+
217+
def item_description(self, item: dict[str, Any]) -> str:
218+
"""Return the release version and release date as the item description."""
219+
version = item.get("version", "Unknown")
220+
release_date = item.get("release_date", "Unknown")
221+
return f"Version: {version}, Release Date: {release_date}"
222+
223+
def item_link(self, item: dict[str, Any]) -> str:
224+
"""Return the URL to the release page on python.org."""
225+
return reverse("downloads:download_release_detail", args=[item.get("slug", "")])
226+
227+
@staticmethod
228+
def item_pubdate(item: dict[str, Any]) -> datetime:
229+
"""Return the release date as the item publication date."""
230+
try:
231+
release_date = datetime.strptime(
232+
item.get("release_date", ""), "%Y-%m-%dT%H:%M:%SZ"
233+
)
234+
return pytz.utc.localize(release_date)
235+
except ValueError:
236+
logger.error(
237+
f"Invalid release date format for item: {item.get('name', 'Unknown')}"
238+
)
239+
return pytz.utc.localize(datetime.now())
240+
241+
@staticmethod
242+
def item_guid(item: dict[str, Any]) -> str:
243+
"""Return the release URI as the item GUID."""
244+
return item.get("resource_uri", "")
245+
246+
def create_url(self, path: str) -> str:
247+
"""Create a full URL using the current site domain."""
248+
current_site = get_current_site(self.request)
249+
scheme = "https" if self.request.is_secure() else "http"
250+
return f"{scheme}://{current_site.domain}{path}"

0 commit comments

Comments
 (0)