22from enum import StrEnum
33
44from django .conf import settings
5- from django .urls import reverse
5+ from django .urls import NoReverseMatch , reverse
66
7+ from libraries .constants import LATEST_RELEASE_URL_PATH_STR
8+ from libraries .utils import get_version_from_cookie
79from versions .models import Version
810
911
@@ -12,6 +14,136 @@ def current_version(request):
1214 return {"current_version" : Version .objects .most_recent ()}
1315
1416
17+ def selected_version (request ):
18+ """User's active Boost version + data to render the navbar version dropdown.
19+
20+ Resolution priority for `selected_version`:
21+ 1. URL `version_slug` kwarg (on `/releases/`, `/libraries/`, `/library/` routes)
22+ 2. `boost_version` cookie
23+ 3. Most recent release (fallback)
24+
25+ When the version comes from the URL, the dropdown renders anchor links that
26+ swap the version segment of the current path — shareable. Otherwise it
27+ renders POST forms that write the cookie without navigating.
28+
29+ Examples
30+ --------
31+ GET /releases/1.88.0/ (no cookie)
32+ selected_version -> Version(slug="boost-1-88-0")
33+ selected_version_is_url_driven -> True (URL mode: render <a>s)
34+ selected_version_is_explicit -> True (button reads "1.88.0")
35+ selected_version_label -> "1.88.0"
36+ version_dropdown_options[i] -> Version with `.href` attached,
37+ e.g. "/releases/1.89.0/"
38+ latest_href -> "/releases/latest/"
39+
40+ GET / (cookie boost_version="boost-1-87-0")
41+ selected_version -> Version(slug="boost-1-87-0")
42+ selected_version_is_url_driven -> False (cookie mode: render <form>s)
43+ selected_version_is_explicit -> True (button reads "1.87.0")
44+ selected_version_label -> "1.87.0"
45+ version_dropdown_options[i] -> Version (no `.href` needed)
46+ latest_href -> ""
47+
48+ GET / (no cookie)
49+ selected_version -> Version.objects.most_recent()
50+ selected_version_is_url_driven -> False
51+ selected_version_is_explicit -> False (button reads "Latest")
52+ selected_version_label -> "Latest"
53+
54+ GET /library/1.88.0/foo/ (target version "boost-1-70-0" predates "foo")
55+ version_dropdown_options[…].href for that version
56+ -> "/library/1.70.0/foo/"
57+ (plain version swap; target view
58+ renders the missing-version state)
59+ """
60+ url_version_slug = None
61+ resolver_match = getattr (request , "resolver_match" , None )
62+ if resolver_match :
63+ url_version_slug = resolver_match .kwargs .get ("version_slug" )
64+
65+ is_url_driven = bool (url_version_slug )
66+ cookie_slug = get_version_from_cookie (request )
67+
68+ resolved_slug = url_version_slug or cookie_slug
69+ version = None
70+ if resolved_slug and resolved_slug != LATEST_RELEASE_URL_PATH_STR :
71+ version = Version .objects .filter (slug = resolved_slug ).first ()
72+ if version is None :
73+ version = Version .objects .most_recent ()
74+
75+ is_explicit_non_latest_url = bool (
76+ url_version_slug and url_version_slug != LATEST_RELEASE_URL_PATH_STR
77+ )
78+ is_explicit_cookie = bool (
79+ not url_version_slug
80+ and cookie_slug
81+ and cookie_slug != LATEST_RELEASE_URL_PATH_STR
82+ )
83+ is_explicit = is_explicit_non_latest_url or is_explicit_cookie
84+
85+ label = version .display_name if (is_explicit and version ) else "Latest"
86+
87+ # Materialize: _annotate_option_hrefs mutates each option with a `.href`.
88+ options = list (Version .objects .get_dropdown_versions ())
89+
90+ latest_href = ""
91+ if is_url_driven and resolver_match and resolver_match .view_name :
92+ latest_href = _annotate_option_hrefs (
93+ view_name = resolver_match .view_name ,
94+ url_kwargs = dict (resolver_match .kwargs ),
95+ options = options ,
96+ )
97+
98+ return {
99+ "selected_version" : version ,
100+ "selected_version_is_url_driven" : is_url_driven ,
101+ "selected_version_is_explicit" : is_explicit ,
102+ "selected_version_label" : label ,
103+ "version_dropdown_options" : options ,
104+ "latest_href" : latest_href ,
105+ }
106+
107+
108+ def _annotate_option_hrefs (* , view_name , url_kwargs , options ):
109+ """Attach `.href` to each option for the current page; return the latest's.
110+
111+ The swap is a plain `reverse()` with the option's version substituted in —
112+ on a library detail page at `/library/1.88.0/foo/`, picking 1.70.0 yields
113+ `/library/1.70.0/foo/` even if that library didn't exist in 1.70.0. The
114+ target view is responsible for rendering the empty / "not available" state.
115+
116+ Examples
117+ --------
118+ Current URL /releases/1.88.0/
119+ view_name = "release-detail"
120+ url_kwargs = {"version_slug": "boost-1-88-0"}
121+ → each option gets .href = "/releases/<that version>/"
122+ → returns "/releases/latest/"
123+
124+ Current URL /libraries/1.88.0/grid/containers/
125+ view_name = "libraries-list"
126+ url_kwargs = {"version_slug": "boost-1-88-0",
127+ "library_view_str": "grid",
128+ "category_slug": "containers"}
129+ → each option gets .href = "/libraries/<that version>/grid/containers/"
130+ → returns "/libraries/latest/grid/containers/"
131+ """
132+ for v in options :
133+ try :
134+ v .href = reverse (view_name , kwargs = {** url_kwargs , "version_slug" : v .slug })
135+ except NoReverseMatch :
136+ v .href = ""
137+
138+ try :
139+ return reverse (
140+ view_name ,
141+ kwargs = {** url_kwargs , "version_slug" : LATEST_RELEASE_URL_PATH_STR },
142+ )
143+ except NoReverseMatch :
144+ return ""
145+
146+
15147class NavItem (StrEnum ):
16148 LIBRARIES = "libraries"
17149 LEARN = "learn"
0 commit comments