diff --git a/apps/downloads/models.py b/apps/downloads/models.py index 8af0a1c1d..e3829fcdb 100644 --- a/apps/downloads/models.py +++ b/apps/downloads/models.py @@ -114,6 +114,40 @@ def get_absolute_url(self): return self.release_page.get_absolute_url() return reverse("download:download_release_detail", kwargs={"release_slug": self.slug}) + @property + def corrected_release_notes_url(self): + """Return the release notes URL, converting dead hg.python.org links to GitHub. + + Old Mercurial-hosted URLs (hg.python.org) are no longer reachable. + This property remaps them to their equivalent paths on GitHub so that + the "Release notes" links on the downloads page still work for legacy + releases (3.3.6 and earlier). + + Examples:: + + http://hg.python.org/cpython/file/v3.3.6/Misc/NEWS + → https://github.com/python/cpython/blob/v3.3.6/Misc/NEWS + + http://hg.python.org/cpython/raw-file/v2.7.3/Misc/NEWS + → https://raw.githubusercontent.com/python/cpython/v2.7.3/Misc/NEWS + """ + url = self.release_notes_url + if not url: + return url + for prefix in ( + "http://hg.python.org/cpython/file/", + "https://hg.python.org/cpython/file/", + ): + if url.startswith(prefix): + return "https://github.com/python/cpython/blob/" + url[len(prefix):] + for prefix in ( + "http://hg.python.org/cpython/raw-file/", + "https://hg.python.org/cpython/raw-file/", + ): + if url.startswith(prefix): + return "https://raw.githubusercontent.com/python/cpython/" + url[len(prefix):] + return url + def download_file_for_os(self, os_slug): """Given an OS slug return the appropriate download file.""" try: @@ -430,3 +464,4 @@ class Meta: violation_error_message="All file URLs must begin with 'https://www.python.org/'", ), ] + diff --git a/apps/downloads/templates/downloads/index.html b/apps/downloads/templates/downloads/index.html index d87b32823..1c04f5df0 100644 --- a/apps/downloads/templates/downloads/index.html +++ b/apps/downloads/templates/downloads/index.html @@ -76,7 +76,7 @@

Looking for a specific release?

{{ r.name }} {{ r.release_date|date }} Download - Release notes + {% if r.corrected_release_notes_url %}Release notes{% else %}Release notes{% endif %} {% endfor %} @@ -127,3 +127,4 @@

Looking for a specific release?

{% box 'download-pgp' %} {% endblock content %} + diff --git a/apps/downloads/tests/test_models.py b/apps/downloads/tests/test_models.py index d1d4c97b3..159c17e18 100644 --- a/apps/downloads/tests/test_models.py +++ b/apps/downloads/tests/test_models.py @@ -311,3 +311,79 @@ def test_release_file_urls_not_python_dot_org(self): name="Windows installer draft", **kwargs, ) + +class ReleaseNotesURLTests(BaseDownloadTests): + """Tests for Release.corrected_release_notes_url property.""" + + def test_hg_url_converted_to_github(self): + """An hg.python.org URL is remapped to its GitHub equivalent.""" + release = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.3.6", + is_published=True, + release_notes_url="http://hg.python.org/cpython/file/v3.3.6/Misc/NEWS", + ) + self.assertEqual( + release.corrected_release_notes_url, + "https://github.com/python/cpython/blob/v3.3.6/Misc/NEWS", + ) + + def test_https_hg_url_also_converted(self): + """An https hg.python.org URL is also remapped to GitHub.""" + release = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.2.6", + is_published=True, + release_notes_url="https://hg.python.org/cpython/file/v3.2.6/Misc/NEWS", + ) + self.assertEqual( + release.corrected_release_notes_url, + "https://github.com/python/cpython/blob/v3.2.6/Misc/NEWS", + ) + + def test_modern_url_returned_unchanged(self): + """A non-hg URL (e.g. already on GitHub) is returned unchanged.""" + url = "https://github.com/python/cpython/blob/v3.12.0/Misc/NEWS.d" + release = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.12.0", + is_published=True, + release_notes_url=url, + ) + self.assertEqual(release.corrected_release_notes_url, url) + + def test_empty_url_returns_empty(self): + """An empty release_notes_url returns an empty string.""" + release = Release.objects.create( + version=Release.PYTHON3, + name="Python 3.11.0", + is_published=True, + release_notes_url="", + ) + self.assertEqual(release.corrected_release_notes_url, "") + + def test_raw_file_hg_url_converted_to_github_raw(self): + """A raw-file hg.python.org URL is remapped to raw.githubusercontent.com.""" + release = Release.objects.create( + version=Release.PYTHON2, + name="Python 2.7.3", + is_published=True, + release_notes_url="http://hg.python.org/cpython/raw-file/v2.7.3/Misc/NEWS", + ) + self.assertEqual( + release.corrected_release_notes_url, + "https://raw.githubusercontent.com/python/cpython/v2.7.3/Misc/NEWS", + ) + + def test_https_raw_file_hg_url_also_converted(self): + """An https raw-file hg.python.org URL is also remapped to raw.githubusercontent.com.""" + release = Release.objects.create( + version=Release.PYTHON2, + name="Python 2.6.9", + is_published=True, + release_notes_url="https://hg.python.org/cpython/raw-file/v2.6.9/Misc/NEWS", + ) + self.assertEqual( + release.corrected_release_notes_url, + "https://raw.githubusercontent.com/python/cpython/v2.6.9/Misc/NEWS", + )