From 6c01a0437bdfdba1b660eeb657f354270b13b881 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 11 May 2026 17:40:03 +0300 Subject: [PATCH] Only show pre-releases when a release exists --- apps/downloads/templatetags/download_tags.py | 17 ++++---- apps/downloads/tests/test_template_tags.py | 46 ++++++++++++++++---- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/apps/downloads/templatetags/download_tags.py b/apps/downloads/templatetags/download_tags.py index c296d7604..9a649b29e 100644 --- a/apps/downloads/templatetags/download_tags.py +++ b/apps/downloads/templatetags/download_tags.py @@ -187,20 +187,20 @@ def render_active_releases(): found_eol = False for release in sorted_releases: + minor = int(release.split(".")[1]) info = release_cycle[release] status = info.get("status", "") first_release = info.get("first_release", "") - if status == "feature" and first_release: - first_release = f"{first_release} (planned)" - - if status == "feature": + if status in ("planned", "feature", "prerelease"): + # Only show pre-release entries once at least one alpha/beta/rc + # has actually shipped (i.e. a published Release exists in the DB). + if not Release.objects.latest_python3(minor): + continue + if first_release: + first_release = f"{first_release} (planned)" status = "pre-release" - # Skip releases not yet in development - if status in ("planned", "prerelease"): - continue - if status == "end-of-life": # Include only the most recent EOL release if found_eol: @@ -208,7 +208,6 @@ def render_active_releases(): found_eol = True # Get last release for EOL versions - minor = int(release.split(".")[1]) last_release = Release.objects.latest_python3(minor) if last_release: status = format_html( diff --git a/apps/downloads/tests/test_template_tags.py b/apps/downloads/tests/test_template_tags.py index a006ce9eb..babdf6e46 100644 --- a/apps/downloads/tests/test_template_tags.py +++ b/apps/downloads/tests/test_template_tags.py @@ -5,6 +5,7 @@ from django.test import TestCase, override_settings from django.urls import reverse +from apps.downloads.models import Release from apps.downloads.templatetags.download_tags import get_eol_info, get_release_cycle_data, render_active_releases from apps.downloads.tests.base import BaseDownloadTests @@ -14,8 +15,8 @@ "3.9": {"status": "end-of-life", "end_of_life": "2025-10-31", "pep": 596}, "3.10": {"status": "security", "end_of_life": "2026-10-04", "pep": 619}, "3.14": {"status": "bugfix", "first_release": "2025-10-07", "end_of_life": "2030-10", "pep": 745}, - "3.15": {"status": "feature", "first_release": "2026-10-01", "end_of_life": "2031-10", "pep": 790}, - "3.16": {"status": "prerelease", "first_release": "2027-10-06", "end_of_life": "2032-10", "pep": 826}, + "3.15": {"status": "prerelease", "first_release": "2026-10-01", "end_of_life": "2031-10", "pep": 790}, + "3.16": {"status": "feature", "first_release": "2027-10-06", "end_of_life": "2032-10", "pep": 826}, "3.17": {"status": "planned", "first_release": "2028-10-05", "end_of_life": "2033-10"}, } @@ -177,6 +178,14 @@ class RenderActiveReleasesTests(BaseDownloadTests): def setUp(self): super().setUp() cache.clear() + # A 3.15 prerelease exists in the DB (e.g. b1 shipped); 3.16 has no + # published release yet, so it should remain hidden. + self.python_3_15_b1 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.15.0b1", + is_published=True, + pre_release=True, + ) @mock.patch("apps.downloads.templatetags.download_tags.get_release_cycle_data") def test_versions_sorted_descending(self, mock_get_data): @@ -186,12 +195,13 @@ def test_versions_sorted_descending(self, mock_get_data): result = render_active_releases() versions = [r["version"] for r in result["releases"]] - # 3.15, 3.14, 3.10, 3.9 (first EOL); 3.8 and 2.7 skipped (older EOL) + # 3.15, 3.14, 3.10, 3.9 (first EOL); 3.8 and 2.7 skipped (older EOL). + # 3.16 ('feature') and 3.17 ('planned') skipped: no published Release in DB. self.assertEqual(versions, ["3.15", "3.14", "3.10", "3.9"]) @mock.patch("apps.downloads.templatetags.download_tags.get_release_cycle_data") - def test_feature_status_becomes_prerelease(self, mock_get_data): - """Test that 'feature' status is converted to 'pre-release'.""" + def test_prerelease_status_relabelled(self, mock_get_data): + """Test that 'prerelease' status is converted to 'pre-release' for display.""" mock_get_data.return_value = MOCK_RELEASE_CYCLE result = render_active_releases() @@ -201,8 +211,8 @@ def test_feature_status_becomes_prerelease(self, mock_get_data): self.assertEqual(prerelease["status"], "pre-release") @mock.patch("apps.downloads.templatetags.download_tags.get_release_cycle_data") - def test_feature_first_release_shows_planned(self, mock_get_data): - """Test that feature releases show (planned) in first_release.""" + def test_prerelease_first_release_shows_planned(self, mock_get_data): + """Test that pre-release entries show (planned) in first_release.""" mock_get_data.return_value = MOCK_RELEASE_CYCLE result = render_active_releases() @@ -237,16 +247,34 @@ def test_eol_status_includes_last_release_link(self, mock_get_data): self.assertIn("