Skip to content

Commit e4883ee

Browse files
authored
feat(#1612): add rss feed for latest downloads (#2569)
* feat: add rss feed for latest downloads * Update downloads/views.py * fix: query DB for releas * fix: query DB for releas * chore: add missing types * fix: update naive datetime, remove staticmethod * fix: remove staticmethod chore: apply formatting and ruuuuuff * chore: no logging needed after working * tests: add them * chore: address code reviews * chore: remove unused imports * chore: remove unused code * chore: remove unused code * fix: purge cdn cache * revert: put the code back, john
1 parent 306a73d commit e4883ee

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

downloads/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ def purge_fastly_download_pages(sender, instance, **kwargs):
272272
if instance.is_published:
273273
# Purge our common pages
274274
purge_url('/downloads/')
275+
purge_url('/downloads/feed.rss')
275276
purge_url('/downloads/latest/python2/')
276277
purge_url('/downloads/latest/python3/')
277278
purge_url('/downloads/macos/')

downloads/tests/test_views.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,3 +554,45 @@ def test_filter_release_file_delete_by_release(self):
554554
headers={"authorization": self.Authorization}
555555
)
556556
self.assertEqual(response.status_code, 405)
557+
558+
class ReleaseFeedTests(BaseDownloadTests):
559+
"""Tests for the downloads/feed.rss endpoint.
560+
561+
Content is ensured via setUp in BaseDownloadTests.
562+
"""
563+
564+
url = reverse("downloads:feed")
565+
566+
567+
def test_endpoint_reachable(self) -> None:
568+
response = self.client.get(self.url)
569+
self.assertEqual(response.status_code, 200)
570+
571+
def test_feed_content(self) -> None:
572+
"""Ensure feed content is as expected.
573+
574+
Some things we want to check:
575+
- Feed title, description, pubdate
576+
- Feed items (releases) are in the correct order
577+
- We get the expected number of releases (10)
578+
"""
579+
response = self.client.get(self.url)
580+
content = response.content.decode()
581+
582+
self.assertIn("Python 2.7.5", content)
583+
self.assertIn("Python 3.10", content)
584+
# Published but hidden show up in the API and thus the feed
585+
self.assertIn("Python 0.0.0", content)
586+
587+
# No unpublished releases
588+
self.assertNotIn("Python 9.7.2", content)
589+
590+
# Pre-releases are shown
591+
self.assertIn("Python 3.9.90", content)
592+
593+
def test_feed_item_count(self) -> None:
594+
response = self.client.get(self.url)
595+
content = response.content.decode()
596+
597+
# In BaseDownloadTests, we create 5 releases, 4 of which are published, 1 of those published are hidden..
598+
self.assertEqual(content.count("<item>"), 4)

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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
from typing import Any
2+
3+
from datetime import datetime
4+
15
from django.db.models import Prefetch
26
from django.urls import reverse
7+
from django.utils import timezone
38
from django.views.generic import DetailView, TemplateView, ListView, RedirectView
49
from django.http import Http404
10+
from django.contrib.syndication.views import Feed
11+
from django.utils.feedgenerator import Rss201rev2Feed
512

613
from .models import OS, Release, ReleaseFile
714

@@ -147,3 +154,45 @@ def get_context_data(self, **kwargs):
147154
)
148155

149156
return context
157+
158+
159+
class ReleaseFeed(Feed):
160+
"""Generate an RSS feed of the latest Python releases.
161+
162+
.. note:: It may seem like these are unused methods, but the superclass uses them
163+
using Django's Syndication framework.
164+
Docs: https://docs.djangoproject.com/en/4.2/ref/contrib/syndication/
165+
"""
166+
167+
feed_type = Rss201rev2Feed
168+
title = "Python Releases"
169+
description = "Latest Python releases from Python.org"
170+
171+
@staticmethod
172+
def link() -> str:
173+
"""Return the URL to the main downloads page."""
174+
return reverse("downloads:download")
175+
176+
def items(self) -> list[dict[str, Any]]:
177+
"""Return the latest Python releases."""
178+
return Release.objects.filter(is_published=True).order_by("-release_date")[:10]
179+
180+
def item_title(self, item: Release) -> str:
181+
"""Return the release name as the item title."""
182+
return item.name
183+
184+
def item_description(self, item: Release) -> str:
185+
"""Return the release version and release date as the item description."""
186+
return f"Version: {item.version}, Release Date: {item.release_date}"
187+
188+
def item_pubdate(self, item: Release) -> datetime | None:
189+
"""Return the release date as the item publication date."""
190+
if item.release_date:
191+
if timezone.is_naive(item.release_date):
192+
return timezone.make_aware(item.release_date)
193+
return item.release_date
194+
return None
195+
196+
def item_guid(self, item: Release) -> str:
197+
"""Return a unique ID for the item based on DB record."""
198+
return str(item.pk)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy