55from django .test import TestCase , override_settings
66from django .urls import reverse
77
8+ from apps .downloads .models import Release
89from apps .downloads .templatetags .download_tags import get_eol_info , get_release_cycle_data , render_active_releases
910from apps .downloads .tests .base import BaseDownloadTests
1011
1415 "3.9" : {"status" : "end-of-life" , "end_of_life" : "2025-10-31" , "pep" : 596 },
1516 "3.10" : {"status" : "security" , "end_of_life" : "2026-10-04" , "pep" : 619 },
1617 "3.14" : {"status" : "bugfix" , "first_release" : "2025-10-07" , "end_of_life" : "2030-10" , "pep" : 745 },
17- "3.15" : {"status" : "feature " , "first_release" : "2026-10-01" , "end_of_life" : "2031-10" , "pep" : 790 },
18- "3.16" : {"status" : "prerelease " , "first_release" : "2027-10-06" , "end_of_life" : "2032-10" , "pep" : 826 },
18+ "3.15" : {"status" : "prerelease " , "first_release" : "2026-10-01" , "end_of_life" : "2031-10" , "pep" : 790 },
19+ "3.16" : {"status" : "feature " , "first_release" : "2027-10-06" , "end_of_life" : "2032-10" , "pep" : 826 },
1920 "3.17" : {"status" : "planned" , "first_release" : "2028-10-05" , "end_of_life" : "2033-10" },
2021}
2122
@@ -177,6 +178,14 @@ class RenderActiveReleasesTests(BaseDownloadTests):
177178 def setUp (self ):
178179 super ().setUp ()
179180 cache .clear ()
181+ # A 3.15 prerelease exists in the DB (e.g. b1 shipped); 3.16 has no
182+ # published release yet, so it should remain hidden.
183+ self .python_3_15_b1 = Release .objects .create (
184+ version = Release .PYTHON3 ,
185+ name = "Python 3.15.0b1" ,
186+ is_published = True ,
187+ pre_release = True ,
188+ )
180189
181190 @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
182191 def test_versions_sorted_descending (self , mock_get_data ):
@@ -186,12 +195,13 @@ def test_versions_sorted_descending(self, mock_get_data):
186195 result = render_active_releases ()
187196
188197 versions = [r ["version" ] for r in result ["releases" ]]
189- # 3.15, 3.14, 3.10, 3.9 (first EOL); 3.8 and 2.7 skipped (older EOL)
198+ # 3.15, 3.14, 3.10, 3.9 (first EOL); 3.8 and 2.7 skipped (older EOL).
199+ # 3.16 ('feature') and 3.17 ('planned') skipped: no published Release in DB.
190200 self .assertEqual (versions , ["3.15" , "3.14" , "3.10" , "3.9" ])
191201
192202 @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
193- def test_feature_status_becomes_prerelease (self , mock_get_data ):
194- """Test that 'feature ' status is converted to 'pre-release'."""
203+ def test_prerelease_status_relabelled (self , mock_get_data ):
204+ """Test that 'prerelease ' status is converted to 'pre-release' for display ."""
195205 mock_get_data .return_value = MOCK_RELEASE_CYCLE
196206
197207 result = render_active_releases ()
@@ -201,8 +211,8 @@ def test_feature_status_becomes_prerelease(self, mock_get_data):
201211 self .assertEqual (prerelease ["status" ], "pre-release" )
202212
203213 @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
204- def test_feature_first_release_shows_planned (self , mock_get_data ):
205- """Test that feature releases show (planned) in first_release."""
214+ def test_prerelease_first_release_shows_planned (self , mock_get_data ):
215+ """Test that pre-release entries show (planned) in first_release."""
206216 mock_get_data .return_value = MOCK_RELEASE_CYCLE
207217
208218 result = render_active_releases ()
@@ -237,16 +247,34 @@ def test_eol_status_includes_last_release_link(self, mock_get_data):
237247 self .assertIn ("<a href=" , status )
238248
239249 @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
240- def test_planned_and_prerelease_releases_excluded (self , mock_get_data ):
241- """Test that planned and prerelease releases are not shown ."""
250+ def test_pre_release_versions_without_shipped_release_excluded (self , mock_get_data ):
251+ """Pre-release statuses ('feature', ' planned') without a shipped release stay hidden ."""
242252 mock_get_data .return_value = MOCK_RELEASE_CYCLE
243253
244254 result = render_active_releases ()
245255
246256 versions = [r ["version" ] for r in result ["releases" ]]
257+ # 3.16 ('feature') and 3.17 ('planned') have no published Release in the DB.
247258 self .assertNotIn ("3.16" , versions )
248259 self .assertNotIn ("3.17" , versions )
249260
261+ @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
262+ def test_feature_version_shown_once_alpha_ships (self , mock_get_data ):
263+ """A 'feature' (in-development on main) version is shown once an alpha ships."""
264+ mock_get_data .return_value = MOCK_RELEASE_CYCLE
265+ Release .objects .create (
266+ version = Release .PYTHON3 ,
267+ name = "Python 3.16.0a1" ,
268+ is_published = True ,
269+ pre_release = True ,
270+ )
271+
272+ result = render_active_releases ()
273+
274+ entry = next (r for r in result ["releases" ] if r ["version" ] == "3.16" )
275+ self .assertEqual (entry ["status" ], "pre-release" )
276+ self .assertEqual (entry ["first_release" ], "2027-10-06 (planned)" )
277+
250278 @mock .patch ("apps.downloads.templatetags.download_tags.get_release_cycle_data" )
251279 def test_api_failure_returns_empty_releases (self , mock_get_data ):
252280 """Test that API failure returns empty releases list."""
0 commit comments