Skip to content

Fix Version model ordering: ascending + version-aware sorting#12894

Open
ericholscher wants to merge 6 commits intomainfrom
claude/fix-rtd-issue-11339-bMuNx
Open

Fix Version model ordering: ascending + version-aware sorting#12894
ericholscher wants to merge 6 commits intomainfrom
claude/fix-rtd-issue-11339-bMuNx

Conversation

@ericholscher
Copy link
Copy Markdown
Member

@ericholscher ericholscher commented Mar 30, 2026

Summary

  • Fix the Version model's default ordering from ["-verbose_name"] (descending) to ["verbose_name"] (ascending), resolving the long-standing bug where version lists in API responses and filter dropdowns appeared in reverse alphabetical order.
  • Add a sort_version_aware() queryset method that pins "latest" first and "stable" second using a SQL CASE expression, applied in the API v3 VersionsViewSet and the build list filter dropdown.
  • Make the sort_version_aware template filter deterministic by adding verbose_name as a secondary sort key, so equal-priority versions sort consistently regardless of input ordering.
  • Fix all tests that implicitly depended on the old descending ordering.

Why this approach

The original issue (#11275) tried to fix this at the model level but was reverted because "tests seemed to break." After thorough analysis, the root causes were:

  1. Tests using .first() without explicit ordering — relied on the descending default to get a specific version. Fixed by making assertions version-agnostic or using explicit lookups.
  2. sort_version_aware depending on input order — non-version strings all got the same sort key (Version("0.01")), so Python's stable sort preserved the (now-changed) input ordering. Fixed by adding verbose_name as a tiebreaker.
  3. Webhook tests asserting call ordertrigger_build calls for multiple versions of the same push were asserted in a specific order that depended on queryset ordering. Fixed with any_order=True.

All production code that queries versions uses explicit filtering (e.g., .filter(slug=LATEST).first()), so the model ordering change is safe. Template code uses sort_version_aware() which does its own sorting independently.

Changes

Core fix:

  • readthedocs/builds/models.pyordering = ["verbose_name"] (was ["-verbose_name"])
  • readthedocs/builds/migrations/0071_version_ordering_ascending.py — Migration with Safe.always() (no-op at DB level)

Version-aware ordering:

  • readthedocs/builds/querysets.py — New sort_version_aware() queryset method using SQL CASE/WHEN
  • readthedocs/api/v3/views.py — Apply sort_version_aware() in VersionsViewSet
  • readthedocs/builds/filters.py — Apply sort_version_aware() in build list version dropdown

Deterministic sorting:

  • readthedocs/projects/templatetags/projects_tags.py — Add verbose_name as secondary sort key in sort_version_aware template filter

Test fixes:

  • readthedocs/api/v3/tests/test_versions.py — Update version list ordering assertions (latest first, v1.0 second)
  • readthedocs/search/tests/conftest.py — Set explicit verbose_name for STABLE version fixture
  • readthedocs/search/tests/test_api.py — Replace hardcoded /en/latest/ assertions with f"/en/{version.slug}/", fix hidden versions test
  • readthedocs/oauth/tests/test_githubapp_webhook.py — Use any_order=True for trigger_build call assertions

Test plan

  • Pre-commit checks (ruff, ruff-format, django-safemigrate) pass
  • Migration check (makemigrations --check) passes
  • Full test suite (tox -e py312) passes
  • API v3 version list returns "latest" first, "stable" second, then alphabetical
  • Build list filter dropdown shows versions in version-aware order

Closes #11339

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4

claude added 2 commits March 30, 2026 03:33
The Version model's Meta ordering was set to ["-verbose_name"]
(descending), which caused version lists in API responses and filter
dropdowns to appear in reverse alphabetical order (z-a instead of a-z).

Change to ascending ["verbose_name"] ordering, which is the expected
behavior for users browsing version lists.

All production code that queries versions uses explicit filtering
(e.g. .filter(slug=LATEST).first()), so this change is safe. Template
code that needs version-aware sorting uses sort_version_aware() which
does its own sorting independent of the model's default ordering.

Closes #11339

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
Add a `sort_version_aware()` queryset method that orders versions with
"latest" first, "stable" second, then remaining versions alphabetically.
This uses a SQL CASE expression so it works at the database level.

Apply this ordering in:
- API v3 VersionsViewSet for user-friendly API responses
- BuildListFilter version dropdown for the dashboard UI

Full semantic version sorting (via comparable_version) requires Python-level
logic and is already handled by the sort_version_aware template filter
where needed. This SQL-level approach covers the most important case:
ensuring special versions appear at the top of lists.

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
@ericholscher ericholscher requested a review from a team as a code owner March 30, 2026 04:44
@ericholscher ericholscher requested a review from stsewd March 30, 2026 04:44
…ate test assertions

- Add `safe = Safe.always()` to migration (required by django-safemigrate
  pre-commit hook). AlterModelOptions is a no-op at the DB level so
  Safe.always() is appropriate.
- Fix ruff-format: collapse single-line method chain in VersionsViewSet
- Update API v3 version list test assertions to expect "latest" first
  and "v1.0" second, matching the new sort_version_aware() ordering.

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
@ericholscher ericholscher marked this pull request as draft March 30, 2026 08:21
@ericholscher ericholscher removed the request for review from stsewd March 30, 2026 08:22
- sort_version_aware: Add verbose_name as secondary sort key to make
  sorting deterministic regardless of input ordering. Previously relied
  on stable sort preserving the (now-changed) model default ordering.

- Search test conftest: Set verbose_name explicitly for STABLE version
  fixture, making queryset ordering predictable.

- Search test_api: Replace hardcoded "/en/latest/" path assertions with
  f"/en/{version.slug}/" so tests work regardless of which version
  .first() returns. Fix hidden versions test to hide all subproject
  versions instead of just the first one.

- GitHub App webhook test: Use any_order=True for trigger_build
  assert_has_calls since the order versions are built shouldn't be a
  guaranteed contract of the webhook handler.

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
@ericholscher ericholscher changed the title Fix Version model default ordering to be ascending Fix Version model ordering: ascending + version-aware sorting Mar 30, 2026
The expected Disallow entries for hidden versions were in descending
order (hidden-2, hidden). With ascending verbose_name ordering, they
now appear in ascending order (hidden, hidden-2).

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
@ericholscher ericholscher marked this pull request as ready for review March 31, 2026 08:41
@ericholscher
Copy link
Copy Markdown
Member Author

ericholscher commented Mar 31, 2026

@agjohnson I think you originally opened this. Figured we let the AI do the grunt work to fix up everything.

Makes it explicit that this is a simplified SQL-level approximation
that only pins latest/stable to the top, not the full semantic version
sorting that the Python-level sort_version_aware template filter does.

https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4
@read-the-docs-community
Copy link
Copy Markdown

Documentation build overview

📚 dev | 🛠️ Build #32052962 | 📁 Comparing cb773fb against latest (e3a4d81)


🔍 Preview build

Show files changed (1 files in total): 📝 1 modified | ➕ 0 added | ➖ 0 deleted
File Status
aws-temporary-credentials.html 📝 modified

@ericholscher ericholscher requested a review from agjohnson April 13, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Version: queryset is ordered backwards

2 participants