From 65942a46da2f9aa95ed6b5b346a01d451a7a735a Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Thu, 20 Nov 2025 23:57:09 +0200
Subject: [PATCH 1/2] Generate page of docs by version
---
pydotorg/urls.py | 1 +
pydotorg/views.py | 114 +++++++++++++++++++++++++++++++++
templates/python/versions.html | 58 +++++++++++++++++
3 files changed, 173 insertions(+)
create mode 100644 templates/python/versions.html
diff --git a/pydotorg/urls.py b/pydotorg/urls.py
index 8a70d5790..bd5496fb6 100644
--- a/pydotorg/urls.py
+++ b/pydotorg/urls.py
@@ -32,6 +32,7 @@
path('getit/', include('downloads.urls', namespace='getit')),
path('downloads/', include('downloads.urls', namespace='download')),
path('doc/', views.DocumentationIndexView.as_view(), name='documentation'),
+ path('doc/versions2/', views.DocsByVersionView.as_view(), name='docs-versions'),
path('blogs/', include('blogs.urls')),
path('inner/', TemplateView.as_view(template_name="python/inner.html"), name='inner'),
diff --git a/pydotorg/views.py b/pydotorg/views.py
index bbc30ec51..8d1bf7f05 100644
--- a/pydotorg/views.py
+++ b/pydotorg/views.py
@@ -1,5 +1,9 @@
+import datetime as dt
import json
import os
+import re
+from collections import defaultdict
+
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.views.generic.base import RedirectView, TemplateView
@@ -67,3 +71,113 @@ def get_redirect_url(self, *args, **kwargs):
settings.AWS_STORAGE_BUCKET_NAME,
image_path,
])
+
+
+class DocsByVersionView(TemplateView):
+ template_name = "python/versions.html"
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+
+ releases = Release.objects.filter(
+ is_published=True,
+ pre_release=False,
+ ).order_by("-release_date")
+
+ # Some releases have no documentation
+ no_docs = {"2.3.6", "2.3.7", "2.4.5", "2.4.6", "2.5.5", "2.5.6"}
+
+ # We'll group releases by major.minor version
+ version_groups = defaultdict(list)
+
+ for release in releases:
+ # Extract version number from name ("Python 3.14.0" -> "3.14.0")
+ version_match = re.match(r"Python ([\d.]+)", release.name)
+ if version_match:
+ full_version = version_match.group(1)
+
+ if full_version in no_docs:
+ continue
+
+ # Get major.minor version ("3.14.0" -> "3.14")
+ version_parts = full_version.split(".")
+ major_minor = f"{version_parts[0]}.{version_parts[1]}"
+
+ # For 3.2.0 and earlier, use X.Y instead of X.Y.0
+ if len(version_parts) == 3:
+ major, minor, patch = map(int, version_parts)
+ # For versions <= 3.2.0 where patch is 0
+ if (major, minor, patch) <= (3, 2, 0) and patch == 0:
+ full_version = major_minor
+
+ release_data = {
+ "stage": full_version,
+ "date": release.release_date.replace(tzinfo=None),
+ }
+ version_groups[major_minor].append(release_data)
+
+ # Add legacy releases not in the database
+ legacy_releases_data = {
+ "2.2": [
+ {"stage": "2.2p1", "date": dt.datetime(2002, 3, 29)},
+ ],
+ "2.1": [
+ {"stage": "2.1.2", "date": dt.datetime(2002, 1, 16)},
+ {"stage": "2.1.1", "date": dt.datetime(2001, 7, 20)},
+ {"stage": "2.1", "date": dt.datetime(2001, 4, 15)},
+ ],
+ "2.0": [
+ {"stage": "2.0", "date": dt.datetime(2000, 10, 16)},
+ ],
+ "1.6": [
+ {"stage": "1.6", "date": dt.datetime(2000, 9, 5)},
+ ],
+ "1.5": [
+ {"stage": "1.5.2p2", "date": dt.datetime(2000, 3, 22)},
+ {"stage": "1.5.2p1", "date": dt.datetime(1999, 7, 6)},
+ {"stage": "1.5.2", "date": dt.datetime(1999, 4, 30)},
+ {"stage": "1.5.1p1", "date": dt.datetime(1998, 8, 6)},
+ {"stage": "1.5.1", "date": dt.datetime(1998, 4, 14)},
+ {"stage": "1.5", "date": dt.datetime(1998, 2, 17)},
+ ],
+ "1.4": [
+ {"stage": "1.4", "date": dt.datetime(1996, 10, 25)},
+ ],
+ }
+
+ # Merge legacy releases in
+ for version, items in legacy_releases_data.items():
+ version_groups[version].extend(items)
+
+ # Convert to list for template and sort releases within each version
+ version_list = []
+ for version, releases in version_groups.items():
+ # Sort x.y.z newest first
+ releases = sorted(
+ releases,
+ key=lambda x: x.get("date", dt.datetime.min),
+ reverse=True,
+ )
+ for release in releases:
+ release["date"] = release["date"].strftime("%-d %B %Y")
+
+ version_list.append(
+ {
+ "version": version,
+ "releases": releases,
+ }
+ )
+
+ # Sort x.y versions (newest first)
+ version_list.sort(
+ key=lambda x: [
+ int(n) if n.isdigit() else n for n in x["version"].split(".")
+ ],
+ reverse=True,
+ )
+
+ context.update({
+ "version_list": version_list,
+ })
+
+ return context
diff --git a/templates/python/versions.html b/templates/python/versions.html
new file mode 100644
index 000000000..b7d6bada0
--- /dev/null
+++ b/templates/python/versions.html
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+{% load boxes %}
+{% load sitetree %}
+
+{% block page_title %}Python documentation by version | {{ SITE_INFO.site_name }}{% endblock %}
+
+{% block body_attributes %}class="python pages default-page"{% endblock %}
+
+{% block breadcrumbs %}
+{% sitetree_breadcrumbs from "main" %}
+{% endblock breadcrumbs %}
+
+{% block content_attributes %}with-left-sidebar{% endblock %}
+
+{% block content %}
+
+
+ Python documentation by version
+
+
+ Some previous versions of the documentation remain available online. Use the list below to select a version to view.
+
+ For unreleased (in development) documentation, see In development versions.
+
+ Release versions
+
+ {% for version_data in version_list %}
+ Python {{ version_data.version }}
+
+ {% for release in version_data.releases %}
+ -
+ {% if release.stage %}
+ Python {{ release.stage }}{% if release.date %}, documentation released on {{ release.date }}{% endif %}
+ {% endif %}
+
+ {% endfor %}
+
+ {% endfor %}
+
+ In development versions
+ The latest, and unreleased, documentation for versions of Python still under development:
+
+
+
+{% endblock content %}
+
+{% block left_sidebar %}
+
+{% endblock left_sidebar %}
From 6653f0b1a0a9fb2ac9a6aaa737436ed343ef009d Mon Sep 17 00:00:00 2001
From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Date: Tue, 25 Nov 2025 22:17:44 +0200
Subject: [PATCH 2/2] documentation released on -> released on
---
templates/python/versions.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/templates/python/versions.html b/templates/python/versions.html
index b7d6bada0..ab9c6a222 100644
--- a/templates/python/versions.html
+++ b/templates/python/versions.html
@@ -30,7 +30,7 @@
Python {{ version_data.version }}
{% for release in version_data.releases %}
{% if release.stage %}
- Python {{ release.stage }}{% if release.date %}, documentation released on {{ release.date }}{% endif %}
+ Python {{ release.stage }}{% if release.date %}, released on {{ release.date }}{% endif %}
{% endif %}
{% endfor %}