From 5df244b5196f42ef21e67eac3d1714feba77e537 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:44:48 +0200 Subject: [PATCH 1/2] Add tests for downloads/latest/python3.x --- downloads/tests/base.py | 30 ++++++++++++++++++++++++++---- downloads/tests/test_models.py | 26 ++++++++++++++++++++++---- downloads/tests/test_views.py | 21 +++++++++++++++++---- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/downloads/tests/base.py b/downloads/tests/base.py index bcb7905c4..2b5e2c905 100644 --- a/downloads/tests/base.py +++ b/downloads/tests/base.py @@ -1,4 +1,4 @@ -import datetime +import datetime as dt from django.test import TestCase from django.utils import timezone @@ -32,7 +32,7 @@ def setUp(self): is_latest=True, is_published=True, release_page=self.release_275_page, - release_date=timezone.now() - datetime.timedelta(days=-1) + release_date=dt.datetime.fromisoformat("2013-05-15T00:00Z"), ) self.release_275_windows_32bit = ReleaseFile.objects.create( os=self.windows, @@ -102,9 +102,31 @@ def setUp(self): self.python_3 = Release.objects.create( version=Release.PYTHON3, - name='Python 3.10', + name="Python 3.10.19", is_latest=True, is_published=True, show_on_download_page=True, - release_page=self.release_275_page + release_page=self.release_275_page, + release_date=dt.datetime.fromisoformat("2025-10-09T00:00Z"), + ) + + self.python_3_10_18 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.10.18", + is_published=True, + release_date=dt.datetime.fromisoformat("2025-06-03T00:00Z"), + ) + + self.python_3_8_20 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.8.20", + is_published=True, + release_date=dt.datetime.fromisoformat("2024-09-06T00:00Z"), + ) + + self.python_3_8_19 = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.8.19", + is_published=True, + release_date=dt.datetime.fromisoformat("2024-03-19T00:00Z"), ) diff --git a/downloads/tests/test_models.py b/downloads/tests/test_models.py index bd26c3b58..1c8e9ba47 100644 --- a/downloads/tests/test_models.py +++ b/downloads/tests/test_models.py @@ -1,3 +1,5 @@ +import datetime as dt + from ..models import Release, ReleaseFile from .base import BaseDownloadTests @@ -10,14 +12,14 @@ def test_stringification(self): def test_published(self): published_releases = Release.objects.published() - self.assertEqual(len(published_releases), 4) + self.assertEqual(len(published_releases), 7) self.assertIn(self.release_275, published_releases) self.assertIn(self.hidden_release, published_releases) self.assertNotIn(self.draft_release, published_releases) def test_release(self): released_versions = Release.objects.released() - self.assertEqual(len(released_versions), 3) + self.assertEqual(len(released_versions), 6) self.assertIn(self.release_275, released_versions) self.assertIn(self.hidden_release, released_versions) self.assertNotIn(self.draft_release, released_versions) @@ -37,7 +39,7 @@ def test_draft(self): def test_downloads(self): downloads = Release.objects.downloads() - self.assertEqual(len(downloads), 2) + self.assertEqual(len(downloads), 5) self.assertIn(self.release_275, downloads) self.assertNotIn(self.hidden_release, downloads) self.assertNotIn(self.draft_release, downloads) @@ -50,12 +52,28 @@ def test_python2(self): def test_python3(self): versions = Release.objects.python3() - self.assertEqual(len(versions), 3) + self.assertEqual(len(versions), 6) self.assertNotIn(self.release_275, versions) self.assertNotIn(self.draft_release, versions) self.assertIn(self.hidden_release, versions) self.assertIn(self.pre_release, versions) + def test_latest_python3(self): + latest_3 = Release.objects.latest_python3() + self.assertEqual(latest_3, self.python_3) + self.assertNotEqual(latest_3, self.python_3_10_18) + + latest_3_10 = Release.objects.latest_python3(minor_version=10) + self.assertEqual(latest_3_10, self.python_3) + self.assertNotEqual(latest_3_10, self.python_3_10_18) + + latest_3_8 = Release.objects.latest_python3(minor_version=8) + self.assertEqual(latest_3_8, self.python_3_8_20) + self.assertNotEqual(latest_3_8, self.python_3_8_19) + + latest_3_99 = Release.objects.latest_python3(minor_version=99) + self.assertIsNone(latest_3_99) + def test_get_version(self): self.assertEqual(self.release_275.name, 'Python 2.7.5') self.assertEqual(self.release_275.get_version(), '2.7.5') diff --git a/downloads/tests/test_views.py b/downloads/tests/test_views.py index b559a2adc..6a7371af4 100644 --- a/downloads/tests/test_views.py +++ b/downloads/tests/test_views.py @@ -67,6 +67,19 @@ def test_latest_redirects(self): response = self.client.get(url) self.assertRedirects(response, latest_python3.get_absolute_url()) + def test_latest_python3x_redirects(self): + url = reverse("download:download_latest_python3x", kwargs={"minor": "10"}) + response = self.client.get(url) + self.assertRedirects(response, self.python_3.get_absolute_url()) + + url = reverse("download:download_latest_python3x", kwargs={"minor": "8"}) + response = self.client.get(url) + self.assertRedirects(response, self.python_3_8_20.get_absolute_url()) + + url = reverse("download:download_latest_python3x", kwargs={"minor": "99"}) + response = self.client.get(url) + self.assertRedirects(response, reverse("download:download")) + def test_redirect_page_object_to_release_detail_page(self): self.release_275.release_page = None self.release_275.save() @@ -218,13 +231,13 @@ def test_get_release(self): self.assertEqual(response.status_code, 200) content = self.get_json(response) # 'self.draft_release' won't shown here. - self.assertEqual(len(content), 4) + self.assertEqual(len(content), 7) # Login to get all releases. response = self.client.get(url, headers={"authorization": self.Authorization}) self.assertEqual(response.status_code, 200) content = self.get_json(response) - self.assertEqual(len(content), 5) + self.assertEqual(len(content), 8) self.assertFalse(content[0]['is_latest']) def test_post_release(self): @@ -594,5 +607,5 @@ def test_feed_item_count(self) -> None: response = self.client.get(self.url) content = response.content.decode() - # In BaseDownloadTests, we create 5 releases, 4 of which are published, 1 of those published are hidden.. - self.assertEqual(content.count(""), 4) + # In BaseDownloadTests, we create 8 releases, 7 of which are published, 1 of those published are hidden.. + self.assertEqual(content.count(""), 7) From c580bb537d8daa090d9a0d7f2ad86073e57fdb38 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:49:32 +0200 Subject: [PATCH 2/2] Refactor DownloadLatestPython3x into DownloadLatestPython3 --- downloads/urls.py | 2 +- downloads/views.py | 25 +++---------------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/downloads/urls.py b/downloads/urls.py index 4b2d573bf..75dcef211 100644 --- a/downloads/urls.py +++ b/downloads/urls.py @@ -5,7 +5,7 @@ urlpatterns = [ re_path(r'latest/python2/?$', views.DownloadLatestPython2.as_view(), name='download_latest_python2'), re_path(r'latest/python3/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3'), - re_path(r'latest/python3\.(?P\d+)/?$', views.DownloadLatestPython3x.as_view(), name='download_latest_python3x'), + re_path(r'latest/python3\.(?P\d+)/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3x'), re_path(r'latest/pymanager/?$', views.DownloadLatestPyManager.as_view(), name='download_latest_pymanager'), re_path(r'latest/?$', views.DownloadLatestPython3.as_view(), name='download_latest_python3'), path('operating-systems/', views.DownloadFullOSList.as_view(), name='download_full_os_list'), diff --git a/downloads/views.py b/downloads/views.py index d19f0e868..9605cec75 100644 --- a/downloads/views.py +++ b/downloads/views.py @@ -30,40 +30,21 @@ def get_redirect_url(self, **kwargs): class DownloadLatestPython3(RedirectView): - """ Redirect to latest Python 3 release """ - permanent = False - - def get_redirect_url(self, **kwargs): - try: - latest_python3 = Release.objects.latest_python3() - except Release.DoesNotExist: - latest_python3 = None + """Redirect to latest Python 3 release, optionally for a specific minor""" - if latest_python3: - return latest_python3.get_absolute_url() - else: - return reverse('download') - - -class DownloadLatestPython3x(RedirectView): - """ Redirect to latest Python 3.x release for a specific minor version """ permanent = False def get_redirect_url(self, **kwargs): minor_version = kwargs.get('minor') - if not minor_version: - return reverse('downloads:download') - try: - minor_version_int = int(minor_version) + minor_version_int = int(minor_version) if minor_version else None latest_release = Release.objects.latest_python3(minor_version_int) except (ValueError, Release.DoesNotExist): latest_release = None if latest_release: return latest_release.get_absolute_url() - else: - return reverse('downloads:download') + return reverse("downloads:download") class DownloadLatestPyManager(RedirectView):