Fix Version model ordering: ascending + version-aware sorting#12894
Open
ericholscher wants to merge 6 commits intomainfrom
Open
Fix Version model ordering: ascending + version-aware sorting#12894ericholscher wants to merge 6 commits intomainfrom
ericholscher wants to merge 6 commits intomainfrom
Conversation
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
…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
- 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
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
Member
Author
|
@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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Versionmodel'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.sort_version_aware()queryset method that pins "latest" first and "stable" second using a SQLCASEexpression, applied in the API v3VersionsViewSetand the build list filter dropdown.sort_version_awaretemplate filter deterministic by addingverbose_nameas a secondary sort key, so equal-priority versions sort consistently regardless of input 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:
.first()without explicit ordering — relied on the descending default to get a specific version. Fixed by making assertions version-agnostic or using explicit lookups.sort_version_awaredepending 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 addingverbose_nameas a tiebreaker.trigger_buildcalls for multiple versions of the same push were asserted in a specific order that depended on queryset ordering. Fixed withany_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 usessort_version_aware()which does its own sorting independently.Changes
Core fix:
readthedocs/builds/models.py—ordering = ["verbose_name"](was["-verbose_name"])readthedocs/builds/migrations/0071_version_ordering_ascending.py— Migration withSafe.always()(no-op at DB level)Version-aware ordering:
readthedocs/builds/querysets.py— Newsort_version_aware()queryset method using SQLCASE/WHENreadthedocs/api/v3/views.py— Applysort_version_aware()inVersionsViewSetreadthedocs/builds/filters.py— Applysort_version_aware()in build list version dropdownDeterministic sorting:
readthedocs/projects/templatetags/projects_tags.py— Addverbose_nameas secondary sort key insort_version_awaretemplate filterTest fixes:
readthedocs/api/v3/tests/test_versions.py— Update version list ordering assertions (latest first, v1.0 second)readthedocs/search/tests/conftest.py— Set explicitverbose_namefor STABLE version fixturereadthedocs/search/tests/test_api.py— Replace hardcoded/en/latest/assertions withf"/en/{version.slug}/", fix hidden versions testreadthedocs/oauth/tests/test_githubapp_webhook.py— Useany_order=Truefor trigger_build call assertionsTest plan
makemigrations --check) passestox -e py312) passesCloses #11339
https://claude.ai/code/session_016gatuCMhRD583kdEhwugk4