mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
Make URLs more consistent, refactor libraries/releases (#1489)
This commit is contained in:
@@ -10,6 +10,7 @@ from django.views.generic import TemplateView
|
||||
|
||||
from config.settings import JDOODLE_API_CLIENT_ID, JDOODLE_API_CLIENT_SECRET
|
||||
from core.calendar import extract_calendar_events, events_by_month, get_calendar
|
||||
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
||||
from libraries.models import Category, Library
|
||||
from news.models import Entry
|
||||
from versions.models import Version
|
||||
@@ -37,6 +38,7 @@ class HomepageView(TemplateView):
|
||||
context["num_months"] = len(context["events"])
|
||||
else:
|
||||
context["num_months"] = 0
|
||||
context["LATEST_RELEASE_URL_PATH_STR"] = LATEST_RELEASE_URL_PATH_STR
|
||||
return context
|
||||
|
||||
def get_events(self):
|
||||
|
||||
@@ -140,7 +140,7 @@ TEMPLATES = [
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"core.context_processors.current_release",
|
||||
"core.context_processors.current_version",
|
||||
"core.context_processors.debug",
|
||||
],
|
||||
"loaders": [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path, re_path
|
||||
from django.urls import include, path, re_path, register_converter
|
||||
from django.views.generic import TemplateView
|
||||
from rest_framework import routers
|
||||
|
||||
@@ -32,9 +32,7 @@ from core.views import (
|
||||
from libraries.api import LibrarySearchView
|
||||
from libraries.views import (
|
||||
LibraryDetail,
|
||||
LibraryList,
|
||||
LibraryListByCategory,
|
||||
LibraryListMini,
|
||||
LibraryListDispatcher,
|
||||
)
|
||||
from news.feeds import AtomNewsFeed, RSSNewsFeed
|
||||
from news.views import (
|
||||
@@ -71,6 +69,7 @@ from users.views import (
|
||||
DeleteImmediatelyView,
|
||||
)
|
||||
from versions.api import ImportVersionsView, VersionViewSet
|
||||
from versions.converters import BoostVersionSlugConverter
|
||||
from versions.feeds import AtomVersionFeed, RSSVersionFeed
|
||||
from versions.views import (
|
||||
InProgressReleaseNotesView,
|
||||
@@ -79,13 +78,14 @@ from versions.views import (
|
||||
VersionDetail,
|
||||
)
|
||||
|
||||
register_converter(BoostVersionSlugConverter, "boostversionslug")
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
|
||||
router.register(r"users", UserViewSet, basename="users")
|
||||
router.register(r"versions", VersionViewSet, basename="versions")
|
||||
router.register(r"libraries", LibrarySearchView, basename="libraries")
|
||||
|
||||
|
||||
urlpatterns = (
|
||||
[
|
||||
path("", HomepageView.as_view(), name="home"),
|
||||
@@ -157,7 +157,11 @@ urlpatterns = (
|
||||
InProgressReleaseNotesView.as_view(),
|
||||
name="release-in-progress",
|
||||
),
|
||||
path("releases/<slug:slug>/", VersionDetail.as_view(), name="release-detail"),
|
||||
path(
|
||||
"releases/<boostversionslug:version_slug>/",
|
||||
VersionDetail.as_view(),
|
||||
name="release-detail",
|
||||
),
|
||||
path(
|
||||
"donate/",
|
||||
TemplateView.as_view(template_name="donate/donate.html"),
|
||||
@@ -168,27 +172,25 @@ urlpatterns = (
|
||||
TemplateView.as_view(template_name="style_guide.html"),
|
||||
name="style-guide",
|
||||
),
|
||||
path("libraries/", LibraryListDispatcher.as_view(), name="libraries"),
|
||||
path(
|
||||
"libraries/by-category/",
|
||||
LibraryListByCategory.as_view(),
|
||||
name="libraries-by-category",
|
||||
),
|
||||
path("libraries/", LibraryList.as_view(), name="libraries"),
|
||||
path("libraries/mini/", LibraryListMini.as_view(), name="libraries-mini"),
|
||||
path("libraries/grid/", LibraryList.as_view(), name="libraries-grid"),
|
||||
path(
|
||||
"libraries/<slug:slug>/<slug:version_slug>/",
|
||||
LibraryDetail.as_view(),
|
||||
name="library-detail-by-version",
|
||||
"libraries/<boostversionslug:version_slug>/<str:library_view_str>/",
|
||||
LibraryListDispatcher.as_view(),
|
||||
name="libraries-list",
|
||||
),
|
||||
path(
|
||||
"libraries/<slug:slug>/",
|
||||
"libraries/<boostversionslug:version_slug>/<str:library_view_str>/<slug:category_slug>/",
|
||||
LibraryListDispatcher.as_view(),
|
||||
name="libraries-list",
|
||||
),
|
||||
path(
|
||||
"library/<boostversionslug:version_slug>/<slug:library_slug>/",
|
||||
LibraryDetail.as_view(),
|
||||
name="library-detail",
|
||||
),
|
||||
# Redirect for '/libs/' legacy boost.org urls.
|
||||
re_path(
|
||||
r"^libs/(?P<slug>[-\w]+)/?$",
|
||||
r"^libs/(?P<library_slug>[-\w]+)/?$",
|
||||
LibraryDetail.as_view(redirect_to_docs=True),
|
||||
name="library-docs-redirect",
|
||||
),
|
||||
|
||||
@@ -3,10 +3,9 @@ from django.conf import settings
|
||||
from versions.models import Version
|
||||
|
||||
|
||||
def current_release(request):
|
||||
def current_version(request):
|
||||
"""Custom context processor that adds the current release to the context"""
|
||||
current_release = Version.objects.most_recent()
|
||||
return {"current_release": current_release}
|
||||
return {"current_version": Version.objects.most_recent()}
|
||||
|
||||
|
||||
def debug(request):
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
from core.context_processors import current_release
|
||||
from core.context_processors import current_version
|
||||
|
||||
|
||||
def test_current_release_context(
|
||||
def test_current_version_context(
|
||||
version, beta_version, inactive_version, old_version, rf
|
||||
):
|
||||
"""Test the current_release context processor. Making the other versions
|
||||
"""Test the current_version context processor. Making the other versions
|
||||
ensures that the most_recent() method returns the correct version."""
|
||||
request = rf.get("/")
|
||||
context = current_release(request)
|
||||
assert "current_release" in context
|
||||
assert context["current_release"] == version
|
||||
context = current_version(request)
|
||||
assert "current_version" in context
|
||||
assert context["current_version"] == version
|
||||
|
||||
@@ -352,8 +352,9 @@ README_MISSING = (
|
||||
"consider contributing one."
|
||||
)
|
||||
|
||||
DEFAULT_LIBRARIES_LANDING_VIEW = "libraries-grid"
|
||||
DEFAULT_LIBRARIES_LANDING_VIEW = "grid"
|
||||
SELECTED_BOOST_VERSION_COOKIE_NAME = "boost_version"
|
||||
SELECTED_LIBRARY_VIEW_COOKIE_NAME = "library_view"
|
||||
# change this to switch from /libraries/align/release/ to /libraries/align/latest/
|
||||
LATEST_RELEASE_URL_PATH_STR = "release"
|
||||
LATEST_RELEASE_URL_PATH_STR = "latest"
|
||||
LEGACY_LATEST_RELEASE_URL_PATH_STR = "release"
|
||||
VERSION_SLUG_PREFIX = "boost-"
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import structlog
|
||||
from django.urls import reverse
|
||||
|
||||
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
||||
from libraries.utils import determine_selected_boost_version
|
||||
from versions.models import Version
|
||||
|
||||
logger = structlog.get_logger()
|
||||
@@ -15,53 +12,37 @@ class VersionAlertMixin:
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
current_release = Version.objects.most_recent()
|
||||
url_name = self.request.resolver_match.url_name
|
||||
url_names_version_slug_override = {
|
||||
"library-detail": "version_slug",
|
||||
"library-detail-by-version": "version_slug",
|
||||
}
|
||||
version_slug_name = url_names_version_slug_override.get(url_name, "slug")
|
||||
version_slug = self.kwargs.get(
|
||||
version_slug_name,
|
||||
self.request.GET.get("version"),
|
||||
if url_name in {"libraries", "releases-most-recent"}:
|
||||
return context
|
||||
current_version_kwargs = self.kwargs.copy()
|
||||
current_version_kwargs.update({"version_slug": LATEST_RELEASE_URL_PATH_STR})
|
||||
context["version_alert_url"] = reverse(url_name, kwargs=current_version_kwargs)
|
||||
context["version_alert"] = (
|
||||
self.kwargs.get("version_slug") != LATEST_RELEASE_URL_PATH_STR
|
||||
)
|
||||
selected_boost_version = determine_selected_boost_version(
|
||||
version_slug, self.request
|
||||
)
|
||||
if not selected_boost_version:
|
||||
selected_boost_version = LATEST_RELEASE_URL_PATH_STR
|
||||
version_slug = LATEST_RELEASE_URL_PATH_STR
|
||||
try:
|
||||
selected_version = Version.objects.get(slug=selected_boost_version)
|
||||
except Version.DoesNotExist:
|
||||
selected_version = current_release
|
||||
|
||||
def generate_reverse(url_name):
|
||||
if url_name in {
|
||||
"libraries-mini",
|
||||
"libraries-by-category",
|
||||
"libraries-grid",
|
||||
}:
|
||||
url = reverse(url_name)
|
||||
params = {"version": LATEST_RELEASE_URL_PATH_STR}
|
||||
return f"{url}?{urlencode(params)}"
|
||||
elif url_name == "library-detail-by-version":
|
||||
library_slug = self.kwargs.get(
|
||||
"slug"
|
||||
) # only really accurately set on library detail page
|
||||
return reverse("library-detail", args=[library_slug])
|
||||
elif url_name == "release-detail":
|
||||
return reverse(url_name, args=[LATEST_RELEASE_URL_PATH_STR])
|
||||
|
||||
# 'version_str' is representative of what the user has chosen, while 'version'
|
||||
# is the actual version instance that will be used in the template. We use the
|
||||
# value of LATEST_RELEASE_URL_PATH_STR as the default in order to normalize
|
||||
# behavior
|
||||
context["version"] = selected_version
|
||||
context["version_str"] = version_slug
|
||||
context["LATEST_RELEASE_URL_PATH_STR"] = LATEST_RELEASE_URL_PATH_STR
|
||||
context["current_release"] = current_release
|
||||
context["version_alert"] = context["version_str"] != LATEST_RELEASE_URL_PATH_STR
|
||||
context["version_alert_url"] = generate_reverse(url_name)
|
||||
return context
|
||||
|
||||
|
||||
class BoostVersionMixin:
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# todo: replace get_current_library_version on LibraryDetail with this +
|
||||
# prefetch_related
|
||||
context.update(
|
||||
{
|
||||
"version_str": self.kwargs.get("version_slug"),
|
||||
"LATEST_RELEASE_URL_PATH_STR": LATEST_RELEASE_URL_PATH_STR,
|
||||
}
|
||||
)
|
||||
if not context.get("current_version"):
|
||||
context["current_version"] = Version.objects.most_recent()
|
||||
|
||||
if context["version_str"] == LATEST_RELEASE_URL_PATH_STR:
|
||||
context["selected_version"] = context["current_version"]
|
||||
elif context["version_str"]:
|
||||
context["selected_version"] = Version.objects.get(
|
||||
slug=context["version_str"]
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
@@ -13,6 +13,7 @@ def library(db):
|
||||
return baker.make(
|
||||
"libraries.Library",
|
||||
name="multi_array",
|
||||
slug="multi_array",
|
||||
description=(
|
||||
"Boost.MultiArray provides a generic N-dimensional array concept "
|
||||
"definition and common implementations of that interface."
|
||||
|
||||
@@ -3,10 +3,10 @@ import pytest
|
||||
from django.test import RequestFactory
|
||||
from model_bakery import baker
|
||||
from libraries.mixins import VersionAlertMixin
|
||||
from libraries.views import LibraryList
|
||||
from libraries.views import LibraryListBase
|
||||
|
||||
|
||||
class MockView(LibraryList, VersionAlertMixin):
|
||||
class MockView(LibraryListBase, VersionAlertMixin):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -9,9 +9,11 @@ from ..models import Library
|
||||
from versions.models import Version
|
||||
|
||||
|
||||
def test_library_list(library_version, tp, url_name="libraries"):
|
||||
def test_library_list(library_version, tp, url_name="libraries", request_kwargs=None):
|
||||
"""GET /libraries/"""
|
||||
# Create a version with a library
|
||||
if not request_kwargs:
|
||||
request_kwargs = {}
|
||||
last_year = library_version.version.release_date - datetime.timedelta(days=365)
|
||||
v2 = baker.make(
|
||||
"versions.Version", name="boost-1.78.0", release_date=last_year, beta=False
|
||||
@@ -26,105 +28,114 @@ def test_library_list(library_version, tp, url_name="libraries"):
|
||||
v_no_libraries = baker.make(
|
||||
"versions.Version", name="boost-1.0.0", release_date=last_year, beta=False
|
||||
)
|
||||
url = tp.reverse(url_name, **request_kwargs)
|
||||
res = tp.get(url, **request_kwargs)
|
||||
|
||||
res = tp.get(url_name)
|
||||
if url_name == "libraries":
|
||||
tp.response_302(res)
|
||||
return
|
||||
tp.response_200(res)
|
||||
assert "library_list" in res.context
|
||||
assert library_version.library in res.context["library_list"]
|
||||
assert lib2 not in res.context["library_list"]
|
||||
assert "object_list" in res.context
|
||||
assert library_version in res.context["object_list"]
|
||||
assert lib2 not in res.context["object_list"]
|
||||
assert v_no_libraries not in res.context["versions"]
|
||||
|
||||
|
||||
def test_library_root_redirect_to_grid(tp):
|
||||
"""GET /"""
|
||||
"""GET /libraries/"""
|
||||
res = tp.get("libraries")
|
||||
tp.response_302(res)
|
||||
assert res.url == "/libraries/grid/"
|
||||
assert res.url == "/libraries/latest/grid/"
|
||||
|
||||
|
||||
def test_library_list_no_data(tp):
|
||||
"""GET /libraries/"""
|
||||
"""GET /libraries/latest/grid/"""
|
||||
Library.objects.all().delete()
|
||||
Version.objects.all().delete()
|
||||
res = tp.get("libraries-grid")
|
||||
res = tp.get("/libraries/latest/grid/")
|
||||
tp.response_200(res)
|
||||
|
||||
|
||||
def test_library_list_mini(library_version, tp):
|
||||
"""GET /libraries/mini/"""
|
||||
test_library_list(library_version, tp, url_name="libraries-mini")
|
||||
def test_library_list_list(library_version, tp):
|
||||
"""GET /libraries/latest/list"""
|
||||
test_library_list(
|
||||
library_version,
|
||||
tp,
|
||||
url_name="libraries-list",
|
||||
request_kwargs={"version_slug": "latest", "library_view_str": "list"},
|
||||
)
|
||||
|
||||
|
||||
def test_library_list_mini_no_data(tp):
|
||||
def test_library_list_list_no_data(tp):
|
||||
"""GET /libraries/"""
|
||||
Library.objects.all().delete()
|
||||
Version.objects.all().delete()
|
||||
res = tp.get("libraries-mini")
|
||||
url = tp.reverse("libraries-list", "latest", "list")
|
||||
res = tp.get(url)
|
||||
tp.response_200(res)
|
||||
|
||||
|
||||
def test_library_list_no_pagination(library_version, tp):
|
||||
"""Library list is not paginated."""
|
||||
libs = [
|
||||
lib_versions = [
|
||||
baker.make(
|
||||
"libraries.LibraryVersion",
|
||||
library=baker.make("libraries.Library", name=f"lib-{i}"),
|
||||
version=library_version.version,
|
||||
).library
|
||||
)
|
||||
for i in range(30)
|
||||
] + [library_version.library]
|
||||
res = tp.get("libraries-grid")
|
||||
] + [library_version]
|
||||
url = tp.reverse("libraries-list", "latest", "grid")
|
||||
res = tp.get(url)
|
||||
tp.response_200(res)
|
||||
|
||||
library_list = res.context.get("library_list")
|
||||
assert library_list is not None
|
||||
assert len(library_list) == len(libs)
|
||||
assert all(library in library_list for library in libs)
|
||||
library_version_list = res.context.get("object_list")
|
||||
assert library_version_list is not None
|
||||
assert len(library_version_list) == len(lib_versions)
|
||||
assert all(lv in library_version_list for lv in lib_versions)
|
||||
page_obj = res.context.get("page_obj")
|
||||
assert getattr(page_obj, "paginator", None) is None
|
||||
|
||||
|
||||
def test_library_list_select_category(library_version, category, tp):
|
||||
"""GET /libraries/?category={{ slug }} loads filtered results"""
|
||||
"""GET /libraries/latest/grid/{category_slug}/ loads filtered results"""
|
||||
library_version.library.categories.add(category)
|
||||
# Create a new library version that is not in the selected category
|
||||
new_lib = baker.make("libraries.Library", name="New")
|
||||
new_lib_version = baker.make(
|
||||
"libraries.LibraryVersion", version=library_version.version, library=new_lib
|
||||
)
|
||||
res = tp.get(f"/libraries/grid/?category={category.slug}")
|
||||
res = tp.get(f"/libraries/latest/grid/{category.slug}/")
|
||||
tp.response_200(res)
|
||||
assert library_version.library in res.context["library_list"]
|
||||
assert new_lib_version.library not in res.context["library_list"]
|
||||
assert library_version in res.context["object_list"]
|
||||
assert new_lib_version not in res.context["object_list"]
|
||||
|
||||
|
||||
@pytest.mark.skip(
|
||||
reason="This test is failing due to the way the library list is being filtered"
|
||||
)
|
||||
def test_library_list_select_version(library_version, tp):
|
||||
"""GET /libraries/?version={{ slug }} loads filtered results"""
|
||||
|
||||
"""GET /libraries/{version_slug}/list/ loads filtered results"""
|
||||
new_version = baker.make("versions.Version", name="New")
|
||||
new_lib = baker.make("libraries.Library", name="New")
|
||||
# Create a new library version that is not in the selected version
|
||||
new_lib_version = baker.make(
|
||||
"libraries.LibraryVersion", version=new_version, library=new_lib
|
||||
)
|
||||
res = tp.get(f"/libraries/?version={library_version.version.slug}")
|
||||
res = tp.get(f"/libraries/{library_version.version.slug}/list/")
|
||||
tp.response_200(res)
|
||||
assert library_version.library in res.context["library_list"]
|
||||
assert new_lib_version.library not in res.context["library_list"]
|
||||
assert library_version.library in res.context["object_list"]
|
||||
assert new_lib_version.library not in res.context["object_list"]
|
||||
|
||||
|
||||
def test_library_list_by_category(
|
||||
library_version, category, tp, url="libraries-by-category"
|
||||
):
|
||||
"""GET /libraries/by-category/"""
|
||||
def test_library_list_by_category(library_version, category, tp, url="libraries-list"):
|
||||
"""GET /libraries/latest/categorized/"""
|
||||
# this first part of the test is weird, maybe a change in functionality happened?
|
||||
# the categorized view shows all categories - the category slug is ignored - so
|
||||
# all categories show
|
||||
reverse_url = tp.reverse(url, "latest", "categorized", category.slug)
|
||||
library_version.library.categories.add(category)
|
||||
res = tp.get(url)
|
||||
res = tp.get(reverse_url)
|
||||
tp.response_200(res)
|
||||
assert "library_versions_by_category" in res.context
|
||||
assert "category" in res.context["library_versions_by_category"][0]
|
||||
@@ -136,8 +147,10 @@ def test_library_list_by_category(
|
||||
new_version = baker.make("versions.Version", name="New")
|
||||
new_lib = baker.make("libraries.Library", name="New", categories=[new_category])
|
||||
baker.make("libraries.LibraryVersion", version=new_version, library=new_lib)
|
||||
res = tp.get(f"/libraries/by-category/?version={library_version.version.slug}")
|
||||
url = tp.reverse("libraries-list", library_version.version.slug, "categorized")
|
||||
res = tp.get(url)
|
||||
tp.response_200(res)
|
||||
tp.assertContext("version_slug", library_version.version.slug)
|
||||
assert existing_category in [
|
||||
x["category"] for x in res.context["library_versions_by_category"]
|
||||
]
|
||||
@@ -147,29 +160,29 @@ def test_library_list_by_category(
|
||||
|
||||
|
||||
def test_library_detail(library_version, tp):
|
||||
"""GET /libraries/{slug}/"""
|
||||
"""GET /library/latest/{library_slug}/"""
|
||||
library = library_version.library
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", "latest", library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_200(response)
|
||||
|
||||
|
||||
def test_library_detail_404(library, tp):
|
||||
"""GET /libraries/{slug}/"""
|
||||
"""GET /libraries/latest/{bad_library_slug}/"""
|
||||
# 404 due to bad slug
|
||||
url = tp.reverse("library-detail", "bananas")
|
||||
url = tp.reverse("library-detail", "latest", "bananas")
|
||||
response = tp.get(url)
|
||||
tp.response_404(response)
|
||||
|
||||
# 404 due to no existing version
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", "latest", library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_404(response)
|
||||
|
||||
|
||||
def test_library_docs_redirect(tp, library, library_version):
|
||||
"""
|
||||
GET /libs/{slug}/
|
||||
GET /libs/{library_slug}/
|
||||
Test that redirection occurs when the library has a documentation URL
|
||||
"""
|
||||
url = tp.reverse("library-docs-redirect", library.slug)
|
||||
@@ -181,7 +194,7 @@ def test_library_docs_redirect(tp, library, library_version):
|
||||
|
||||
def test_library_detail_context_get_commit_data_(tp, library_version):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
GET /library/latest/{library_slug}/
|
||||
Test that the commit_data_by_release var appears as expected
|
||||
"""
|
||||
library = library_version.library
|
||||
@@ -197,14 +210,14 @@ def test_library_detail_context_get_commit_data_(tp, library_version):
|
||||
for lv in [lv_a, lv_b, lv_c]:
|
||||
baker.make("libraries.Commit", library_version=lv)
|
||||
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", "latest", library.slug)
|
||||
response = tp.get_check_200(url)
|
||||
assert "commit_data_by_release" in response.context
|
||||
|
||||
|
||||
def test_library_detail_context_get_maintainers(tp, user, library_version):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
GET /libraries/latest/{library_slug}/
|
||||
Test that the maintainers var appears as expected
|
||||
"""
|
||||
library_version.maintainers.add(user)
|
||||
@@ -214,7 +227,7 @@ def test_library_detail_context_get_maintainers(tp, user, library_version):
|
||||
baker.make("libraries.PullRequest", library=library, is_open=True)
|
||||
baker.make("libraries.PullRequest", library=library, is_open=False)
|
||||
baker.make("libraries.PullRequest", library=lib2, is_open=True)
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", "latest", library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_200(response)
|
||||
assert "maintainers" in response.context
|
||||
@@ -226,52 +239,49 @@ def test_library_detail_context_get_documentation_url_no_docs_link(
|
||||
tp, user, library_version
|
||||
):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
Test that the maintainers var appears as expected
|
||||
GET /library/{version_slug}/{library_slug}/
|
||||
"""
|
||||
library_version.documentation_url = None
|
||||
library_version.save()
|
||||
|
||||
library = library_version.library
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", library_version.version.slug, library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_200(response)
|
||||
assert "documentation_url" in response.context
|
||||
assert response.context["documentation_url"] == "/doc/libs/release"
|
||||
assert response.context["documentation_url"] == "/doc/libs/1_79_0"
|
||||
|
||||
|
||||
def test_library_detail_context_get_documentation_url_missing_docs_bool(
|
||||
tp, user, library_version
|
||||
):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
Test that the maintainers var appears as expected
|
||||
GET /library/{version_slug}/{library_slug}/
|
||||
"""
|
||||
library_version.documentation_url = None
|
||||
library_version.missing_docs = True
|
||||
library_version.save()
|
||||
|
||||
library = library_version.library
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", library_version.version.slug, library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_200(response)
|
||||
assert "documentation_url" in response.context
|
||||
assert response.context["documentation_url"] == "/doc/libs/release"
|
||||
assert response.context["documentation_url"] == "/doc/libs/1_79_0"
|
||||
|
||||
|
||||
def test_library_detail_context_get_documentation_url_docs_present(
|
||||
tp, user, library_version
|
||||
):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
Test that the maintainers var appears as expected
|
||||
GET /libraries/{version_slug}/{library_slug}/
|
||||
"""
|
||||
library_version.documentation_url = "https://example.com"
|
||||
library_version.missing_docs = False
|
||||
library_version.save()
|
||||
|
||||
library = library_version.library
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", library_version.version.slug, library.slug)
|
||||
response = tp.get(url)
|
||||
tp.response_200(response)
|
||||
assert "documentation_url" in response.context
|
||||
@@ -279,44 +289,46 @@ def test_library_detail_context_get_documentation_url_docs_present(
|
||||
|
||||
|
||||
def test_libraries_by_version_detail(tp, library_version):
|
||||
"""GET /libraries/{slug}/{version_slug}/"""
|
||||
"""GET /libraries/{version_slug}/{library_slug}/"""
|
||||
res = tp.get(
|
||||
"library-detail-by-version",
|
||||
library_version.library.slug,
|
||||
"library-detail",
|
||||
library_version.version.slug,
|
||||
library_version.library.slug,
|
||||
)
|
||||
tp.response_200(res)
|
||||
assert "version" in res.context
|
||||
assert "current_version" in res.context
|
||||
assert "selected_version" in res.context
|
||||
assert res.context["selected_version"] == library_version.version
|
||||
|
||||
|
||||
def test_libraries_by_version_detail_no_library_found(tp, library_version):
|
||||
"""GET /libraries/{slug}/{version_slug}/"""
|
||||
"""GET /library/{version_slug}/{bad_library_slug}/"""
|
||||
res = tp.get(
|
||||
"library-detail-by-version",
|
||||
"coffee",
|
||||
"library-detail",
|
||||
library_version.version.slug,
|
||||
"coffee",
|
||||
)
|
||||
tp.response_404(res)
|
||||
|
||||
|
||||
def test_libraries_by_version_detail_no_version_found(tp, library_version):
|
||||
"""GET /libraries/{slug}/{version_slug}/"""
|
||||
"""GET /library/{version_slug}/{bad_library_slug}/"""
|
||||
res = tp.get(
|
||||
"library-detail-by-version",
|
||||
library_version.library.slug,
|
||||
"library-detail",
|
||||
000000,
|
||||
library_version.library.slug,
|
||||
)
|
||||
tp.response_404(res)
|
||||
|
||||
|
||||
def test_library_detail_context_missing_readme(tp, user, library_version):
|
||||
"""
|
||||
GET /libraries/{slug}/
|
||||
GET /library/latest/{library_slug}/
|
||||
Test that the missing readme message appears as expected
|
||||
"""
|
||||
|
||||
library = library_version.library
|
||||
url = tp.reverse("library-detail", library.slug)
|
||||
url = tp.reverse("library-detail", "latest", library.slug)
|
||||
|
||||
response = tp.get(url)
|
||||
|
||||
|
||||
@@ -6,12 +6,9 @@ import structlog
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from dateutil.parser import ParserError, parse
|
||||
from django.utils.text import slugify
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from libraries.constants import (
|
||||
DEFAULT_LIBRARIES_LANDING_VIEW,
|
||||
@@ -91,16 +88,6 @@ def write_content_to_tempfile(content):
|
||||
return temp_file
|
||||
|
||||
|
||||
def redirect_to_view_with_params(view_name, params, query_params):
|
||||
"""Redirect to a view with parameters and query parameters."""
|
||||
base_url = reverse(view_name, kwargs=params)
|
||||
query_string = urlencode(query_params)
|
||||
url = base_url
|
||||
if query_string:
|
||||
url = "{}?{}".format(base_url, query_string)
|
||||
return redirect(url)
|
||||
|
||||
|
||||
def get_version_from_url(request):
|
||||
return request.GET.get("version")
|
||||
|
||||
@@ -110,7 +97,7 @@ def get_version_from_cookie(request):
|
||||
|
||||
|
||||
def get_view_from_url(request):
|
||||
return request.GET.get("view")
|
||||
return request.resolver_match.kwargs.get("library_view_str")
|
||||
|
||||
|
||||
def get_view_from_cookie(request):
|
||||
@@ -118,6 +105,9 @@ def get_view_from_cookie(request):
|
||||
|
||||
|
||||
def set_view_in_cookie(response, view):
|
||||
allowed_views = {"grid", "list", "categorized"}
|
||||
if view not in allowed_views:
|
||||
return
|
||||
response.set_cookie(SELECTED_LIBRARY_VIEW_COOKIE_NAME, view)
|
||||
|
||||
|
||||
@@ -146,34 +136,14 @@ def get_prioritized_library_view(request):
|
||||
return url_view or cookie_view or DEFAULT_LIBRARIES_LANDING_VIEW
|
||||
|
||||
|
||||
def build_view_query_params_from_request(request):
|
||||
query_params = {}
|
||||
version = get_prioritized_version(request)
|
||||
category = get_category(request)
|
||||
if version and version != LATEST_RELEASE_URL_PATH_STR:
|
||||
query_params["version"] = version
|
||||
if category:
|
||||
query_params["category"] = category
|
||||
return query_params
|
||||
|
||||
|
||||
def get_category(request):
|
||||
return request.GET.get("category", "")
|
||||
|
||||
|
||||
def build_route_name_for_view(view):
|
||||
return f"libraries-{view}"
|
||||
|
||||
|
||||
def determine_view_from_library_request(request):
|
||||
split_path_info = request.path_info.split("/")
|
||||
return None if split_path_info[-2] == "libraries" else split_path_info[-2]
|
||||
|
||||
|
||||
def determine_selected_boost_version(request_value, request):
|
||||
valid_versions = Version.objects.version_dropdown_strict()
|
||||
version_slug = request_value or get_version_from_cookie(request)
|
||||
if version_slug in [v.slug for v in valid_versions]:
|
||||
if version_slug in [v.slug for v in valid_versions] + [LATEST_RELEASE_URL_PATH_STR]:
|
||||
return version_slug
|
||||
else:
|
||||
logger.warning(f"Invalid version slug in cookies: {version_slug}")
|
||||
@@ -192,9 +162,9 @@ def set_selected_boost_version(version_slug: str, response) -> None:
|
||||
|
||||
|
||||
def library_doc_latest_transform(url):
|
||||
p = re.compile(r"(/doc/libs/)[a-zA-Z0-9_]+([//\S]*)$")
|
||||
p = re.compile(r"^(/doc/libs/)[0-9_]+(/\S+)$")
|
||||
if p.match(url):
|
||||
url = p.sub(r"\1release\2", url)
|
||||
url = p.sub(rf"\1{LATEST_RELEASE_URL_PATH_STR}\2", url)
|
||||
return url
|
||||
|
||||
|
||||
@@ -202,18 +172,15 @@ def get_documentation_url(library_version, latest):
|
||||
"""Get the documentation URL for the current library."""
|
||||
|
||||
def find_documentation_url(library_version):
|
||||
version = library_version.version
|
||||
docs_url = version.documentation_url
|
||||
|
||||
# If we know the library-version docs are missing, return the version docs
|
||||
if library_version.missing_docs:
|
||||
return docs_url
|
||||
return library_version.version.documentation_url
|
||||
# If we have the library-version docs and they are valid, return those
|
||||
elif library_version.documentation_url:
|
||||
return library_version.documentation_url
|
||||
# If we wind up here, return the version docs
|
||||
else:
|
||||
return docs_url
|
||||
return library_version.version.documentation_url
|
||||
|
||||
# Get the URL for the version.
|
||||
url = find_documentation_url(library_version)
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import datetime
|
||||
from types import SimpleNamespace
|
||||
|
||||
import structlog
|
||||
from django.contrib import messages
|
||||
from django.db.models import F, Count, Exists, OuterRef, Prefetch
|
||||
from django.db.models.functions import Lower
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django import urls
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from core.githubhelper import GithubAPIClient
|
||||
from versions.models import Version
|
||||
|
||||
from .constants import README_MISSING
|
||||
from .forms import VersionSelectionForm
|
||||
from .mixins import VersionAlertMixin
|
||||
from .mixins import VersionAlertMixin, BoostVersionMixin
|
||||
from .models import (
|
||||
Category,
|
||||
Commit,
|
||||
@@ -29,118 +27,87 @@ from .models import (
|
||||
LibraryVersion,
|
||||
)
|
||||
from .utils import (
|
||||
redirect_to_view_with_params,
|
||||
get_view_from_cookie,
|
||||
set_view_in_cookie,
|
||||
get_prioritized_library_view,
|
||||
build_view_query_params_from_request,
|
||||
build_route_name_for_view,
|
||||
determine_view_from_library_request,
|
||||
determine_selected_boost_version,
|
||||
set_selected_boost_version,
|
||||
get_documentation_url,
|
||||
)
|
||||
from .constants import LATEST_RELEASE_URL_PATH_STR
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
class LibraryList(VersionAlertMixin, ListView):
|
||||
"""List all of our libraries for a specific Boost version, or default
|
||||
to the current version."""
|
||||
|
||||
queryset = (
|
||||
(
|
||||
Library.objects.prefetch_related("authors", "categories")
|
||||
.all()
|
||||
.order_by("name")
|
||||
)
|
||||
.defer("data")
|
||||
.distinct()
|
||||
)
|
||||
template_name = "libraries/list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
params = self.request.GET.copy()
|
||||
|
||||
# If the user has selected a version, fetch it from the cookies.
|
||||
selected_boost_version = (
|
||||
class LibraryListDispatcher(View):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
view_str = self.kwargs.get("library_view_str")
|
||||
if view_str == "list":
|
||||
view = LibraryVertical.as_view()
|
||||
elif view_str == "categorized":
|
||||
view = LibraryCategorized.as_view()
|
||||
else:
|
||||
# covers both /libraries and /libraries/.../grid[/...]
|
||||
view = LibraryListBase.as_view()
|
||||
version_str = (
|
||||
determine_selected_boost_version(
|
||||
self.request.GET.get("version"), self.request
|
||||
self.kwargs.get("version_slug"), self.request
|
||||
)
|
||||
or LATEST_RELEASE_URL_PATH_STR
|
||||
)
|
||||
# default to the most recent version
|
||||
if selected_boost_version == LATEST_RELEASE_URL_PATH_STR:
|
||||
# If no version is specified, show the most recent version.
|
||||
if not self.kwargs.get("version_slug"):
|
||||
self.kwargs["version_slug"] = version_str
|
||||
return view(request, *args, **self.kwargs) # , *args, **kwargs)
|
||||
|
||||
|
||||
class LibraryListBase(BoostVersionMixin, VersionAlertMixin, ListView):
|
||||
"""Based on LibraryVersion, list all of our libraries in grid format for a specific
|
||||
Boost version, or default to the current version."""
|
||||
|
||||
queryset = LibraryVersion.objects.prefetch_related(
|
||||
"authors", "library", "library__categories"
|
||||
).defer("data")
|
||||
ordering = "library__name"
|
||||
template_name = "libraries/grid_list.html"
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset()
|
||||
version_slug = determine_selected_boost_version(
|
||||
self.kwargs.get("version_slug"), self.request
|
||||
)
|
||||
if version_slug == LATEST_RELEASE_URL_PATH_STR:
|
||||
version = Version.objects.most_recent()
|
||||
if version:
|
||||
selected_boost_version = version.slug
|
||||
else:
|
||||
# Add a message that no data has been imported
|
||||
if not version:
|
||||
messages.add_message(
|
||||
self.request,
|
||||
messages.WARNING,
|
||||
"No data has been imported yet. Please check back later.",
|
||||
)
|
||||
return Library.objects.none()
|
||||
version_slug = version.slug
|
||||
|
||||
queryset = queryset.filter(
|
||||
library_version__version__slug=selected_boost_version
|
||||
)
|
||||
version_filter_args = {"version__slug": version_slug}
|
||||
|
||||
# avoid attempting to look up libraries with blank categories
|
||||
if params.get("category"):
|
||||
queryset = queryset.filter(categories__slug=params.get("category"))
|
||||
no_category_filtering_views = ["categorized"]
|
||||
if (
|
||||
self.kwargs.get("category_slug")
|
||||
and self.kwargs.get("library_view_str") not in no_category_filtering_views
|
||||
):
|
||||
version_filter_args["library__categories__slug"] = self.kwargs.get(
|
||||
"category_slug"
|
||||
)
|
||||
|
||||
return queryset
|
||||
return queryset.filter(**version_filter_args)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# Handle the case where data hasn't been imported yet
|
||||
version = Version.objects.most_recent()
|
||||
version_str = determine_selected_boost_version(
|
||||
self.request.GET.get("version"), self.request
|
||||
context = super().get_context_data(**self.kwargs)
|
||||
context["categories"] = self.get_categories(context["selected_version"])
|
||||
context["versions"] = self.get_versions(
|
||||
current_version=context["current_version"]
|
||||
)
|
||||
if not version_str:
|
||||
version_str = LATEST_RELEASE_URL_PATH_STR
|
||||
if not version:
|
||||
context.update(
|
||||
{
|
||||
"category": None,
|
||||
"version": None,
|
||||
"version_str": version_str,
|
||||
"categories": Category.objects.none(),
|
||||
"versions": Version.objects.none(),
|
||||
"library_version_list": LibraryVersion.objects.none(),
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
if self.request.GET.get("category"):
|
||||
# todo: add tests for sort order
|
||||
if self.kwargs.get("category_slug"):
|
||||
context["category"] = Category.objects.get(
|
||||
slug=self.request.GET["category"]
|
||||
slug=self.kwargs.get("category_slug")
|
||||
)
|
||||
context["categories"] = self.get_categories(context["version"])
|
||||
context["versions"] = self.get_versions()
|
||||
context["version_str"] = version_str
|
||||
# todo: add tests for sort order, consider refactor to queryset use
|
||||
library_versions_qs = (
|
||||
LibraryVersion.objects.filter(
|
||||
version__slug=version_str
|
||||
if version_str != LATEST_RELEASE_URL_PATH_STR
|
||||
else version.slug
|
||||
)
|
||||
.prefetch_related("authors", "library", "library__categories")
|
||||
.order_by("library__name")
|
||||
)
|
||||
if self.request.GET.get("category"):
|
||||
library_versions_qs = library_versions_qs.filter(
|
||||
library__categories__slug=self.request.GET.get("category")
|
||||
)
|
||||
context["library_version_list"] = library_versions_qs
|
||||
context["url_params"] = build_view_query_params_from_request(self.request)
|
||||
|
||||
return context
|
||||
|
||||
@@ -151,7 +118,7 @@ class LibraryList(VersionAlertMixin, ListView):
|
||||
.order_by("name")
|
||||
)
|
||||
|
||||
def get_versions(self):
|
||||
def get_versions(self, current_version):
|
||||
"""
|
||||
Return a queryset of all versions to display in the version dropdown.
|
||||
"""
|
||||
@@ -165,70 +132,82 @@ class LibraryList(VersionAlertMixin, ListView):
|
||||
# Filter out versions with no libraries
|
||||
versions = versions.filter(library_count__gt=0)
|
||||
|
||||
most_recent_version = Version.objects.most_recent()
|
||||
|
||||
# Confirm the most recent v is in the queryset, even if it has no libraries
|
||||
if most_recent_version not in versions:
|
||||
versions = versions | Version.objects.filter(pk=most_recent_version.pk)
|
||||
if current_version and current_version not in versions:
|
||||
versions = versions | Version.objects.filter(pk=current_version.pk)
|
||||
|
||||
# Manually exclude the master and develop branches.
|
||||
versions = versions.exclude(name__in=["develop", "master", "head"])
|
||||
# todo: confirm is redundant with version_dropdown()'s matching exclude
|
||||
# versions = versions.exclude(name__in=["develop", "master", "head"])
|
||||
versions.prefetch_related("library_version")
|
||||
return versions
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Set the selected version in the cookies."""
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
query_params = build_view_query_params_from_request(request)
|
||||
set_selected_boost_version(
|
||||
query_params.get("version", LATEST_RELEASE_URL_PATH_STR), response
|
||||
)
|
||||
# The following conditional practically only applies on "/libraries/", at
|
||||
# which point the redirection will be determined by prioritised view
|
||||
view = determine_view_from_library_request(request)
|
||||
if not view:
|
||||
view = get_prioritized_library_view(request)
|
||||
set_view_in_cookie(response, build_route_name_for_view(view))
|
||||
return redirect_to_view_with_params(view, kwargs, query_params)
|
||||
set_selected_boost_version(self.kwargs.get("version_slug"), response)
|
||||
view = get_prioritized_library_view(request)
|
||||
if request.resolver_match.view_name == "libraries":
|
||||
# todo: remove the following migration block some time after March 1st 2025
|
||||
def update_deprecated_cookie_view(cookie_view, response):
|
||||
deprecated_views = {
|
||||
"libraries-mini": "list",
|
||||
"libraries-grid": "grid",
|
||||
"libraries-by-category": "categorized",
|
||||
}
|
||||
if cookie_view in deprecated_views:
|
||||
cookie_view = deprecated_views[cookie_view]
|
||||
set_view_in_cookie(response, cookie_view)
|
||||
return cookie_view
|
||||
|
||||
view = update_deprecated_cookie_view(view, response)
|
||||
# todo: end of migration block
|
||||
|
||||
# set the cookie in case it has changed
|
||||
set_view_in_cookie(response, view)
|
||||
redirect_args = {
|
||||
"version_slug": self.kwargs.get("version_slug"),
|
||||
"library_view_str": view,
|
||||
}
|
||||
if self.kwargs.get("category_slug"):
|
||||
redirect_args["category_slug"] = self.kwargs.get("category_slug")
|
||||
return redirect("libraries-list", **redirect_args)
|
||||
|
||||
if view != get_view_from_cookie(request):
|
||||
set_view_in_cookie(response, build_route_name_for_view(view))
|
||||
set_view_in_cookie(response, view)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class LibraryListMini(LibraryList):
|
||||
class LibraryVertical(LibraryListBase):
|
||||
"""Flat list version of LibraryList"""
|
||||
|
||||
template_name = "libraries/flat_list.html"
|
||||
template_name = "libraries/vertical_list.html"
|
||||
|
||||
|
||||
class LibraryListByCategory(LibraryList):
|
||||
class LibraryCategorized(LibraryListBase):
|
||||
"""List all Boost libraries sorted by Category."""
|
||||
|
||||
template_name = "libraries/category_list.html"
|
||||
template_name = "libraries/categorized_list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["library_versions_by_category"] = self.get_results_by_category(
|
||||
version=context.get("version")
|
||||
version=context.get("selected_version")
|
||||
)
|
||||
return context
|
||||
|
||||
def get_results_by_category(self, version: Version | None):
|
||||
# Define filter kwargs based on whether version is provided
|
||||
version_filter = {"version": version} if version else {}
|
||||
category_filter = (
|
||||
{"libraries__library_version__version": version} if version else {}
|
||||
)
|
||||
library_versions_qs = LibraryVersion.objects.filter(**version_filter)
|
||||
|
||||
libraries_prefetch = Prefetch(
|
||||
"libraries",
|
||||
queryset=Library.objects.order_by("name").prefetch_related(
|
||||
Prefetch(
|
||||
"library_version",
|
||||
queryset=library_versions_qs,
|
||||
queryset=self.get_queryset(),
|
||||
to_attr="prefetched_library_versions",
|
||||
)
|
||||
),
|
||||
@@ -258,7 +237,7 @@ class LibraryListByCategory(LibraryList):
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
class LibraryDetail(FormMixin, VersionAlertMixin, BoostVersionMixin, DetailView):
|
||||
"""Display a single Library in insolation"""
|
||||
|
||||
form_class = VersionSelectionForm
|
||||
@@ -274,26 +253,24 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
Version.objects.active()
|
||||
.filter(library_version__library=self.object)
|
||||
.distinct()
|
||||
.exclude(name__in=["develop", "master", "head"])
|
||||
.exclude(beta=True)
|
||||
.order_by("-release_date")
|
||||
)
|
||||
|
||||
# Manually exclude feature branches from the version dropdown.
|
||||
context["versions"] = context["versions"].exclude(
|
||||
name__in=["develop", "master", "head"]
|
||||
)
|
||||
|
||||
# Manually exclude beta releases from the version dropdown.
|
||||
context["versions"] = context["versions"].exclude(beta=True)
|
||||
context["LATEST_RELEASE_URL_PATH_NAME"] = LATEST_RELEASE_URL_PATH_STR
|
||||
# Get general data and version-sensitive data
|
||||
library_version = LibraryVersion.objects.get(
|
||||
library=self.get_object(), version=context["version"]
|
||||
library=self.get_object(), version=context["selected_version"]
|
||||
)
|
||||
context["library_version"] = library_version
|
||||
context["documentation_url"] = get_documentation_url(
|
||||
library_version, context["version_str"] == LATEST_RELEASE_URL_PATH_STR
|
||||
)
|
||||
context["github_url"] = self.get_github_url(context["version"])
|
||||
context["github_url"] = (
|
||||
library_version.library_repo_url_for_version
|
||||
if library_version
|
||||
else self.object.github_url
|
||||
)
|
||||
context["authors"] = self.get_related(library_version, "authors")
|
||||
context["maintainers"] = self.get_related(
|
||||
library_version,
|
||||
@@ -312,7 +289,7 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
if getattr(x.commitauthor, "id", None)
|
||||
]
|
||||
top_contributors_release = self.get_top_contributors(
|
||||
version=context["version"],
|
||||
version=context["selected_version"],
|
||||
exclude=exclude_maintainer_ids + exclude_author_ids,
|
||||
)
|
||||
context["top_contributors_release_new"] = [
|
||||
@@ -323,7 +300,7 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
]
|
||||
exclude_top_contributor_ids = [x.id for x in top_contributors_release]
|
||||
context["previous_contributors"] = self.get_previous_contributors(
|
||||
context["version"],
|
||||
context["selected_version"],
|
||||
exclude=exclude_maintainer_ids
|
||||
+ exclude_top_contributor_ids
|
||||
+ exclude_author_ids,
|
||||
@@ -334,10 +311,10 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
# Populate the library description
|
||||
client = GithubAPIClient(repo_slug=self.object.github_repo)
|
||||
context["description"] = (
|
||||
self.object.get_description(client, tag=context["version"].name)
|
||||
self.object.get_description(client, tag=context["selected_version"].name)
|
||||
or README_MISSING
|
||||
)
|
||||
|
||||
context["library_view_str"] = get_prioritized_library_view(self.request)
|
||||
return context
|
||||
|
||||
def get_commit_data_by_release(self):
|
||||
@@ -361,16 +338,16 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
"""Get the current library object from the slug in the URL.
|
||||
If present, use the version_slug to get the right LibraryVersion of the library.
|
||||
Otherwise, default to the most recent version."""
|
||||
slug = self.kwargs.get("slug")
|
||||
library_slug = self.kwargs.get("library_slug")
|
||||
version = self.get_version()
|
||||
|
||||
if not LibraryVersion.objects.filter(
|
||||
version=version, library__slug__iexact=slug
|
||||
version=version, library__slug__iexact=library_slug
|
||||
).exists():
|
||||
raise Http404("No library found matching the query")
|
||||
|
||||
try:
|
||||
obj = self.get_queryset().get(slug__iexact=slug)
|
||||
obj = self.get_queryset().get(slug__iexact=library_slug)
|
||||
except self.model.DoesNotExist:
|
||||
raise Http404("No library found matching the query")
|
||||
return obj
|
||||
@@ -400,13 +377,6 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
|
||||
return commit_data_list
|
||||
|
||||
def get_current_library_version(self, version):
|
||||
"""Return the library-version for the latest version of Boost"""
|
||||
# Avoid raising an error if the library has been removed from the latest version
|
||||
return LibraryVersion.objects.filter(
|
||||
library=self.object, version=version
|
||||
).first()
|
||||
|
||||
def get_github_url(self, version):
|
||||
"""Get the GitHub URL for the current library."""
|
||||
try:
|
||||
@@ -498,10 +468,11 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
def get_version(self):
|
||||
"""Get the version of Boost for the library we're currently looking at."""
|
||||
version_slug = self.kwargs.get("version_slug")
|
||||
if version_slug:
|
||||
return get_object_or_404(Version, slug=version_slug)
|
||||
else:
|
||||
# here we need to check for not version_slug because of redirect_to_docs
|
||||
# where it's not necessarily set by the source request
|
||||
if not version_slug or version_slug == LATEST_RELEASE_URL_PATH_STR:
|
||||
return Version.objects.most_recent()
|
||||
return get_object_or_404(Version, slug=version_slug)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Redirect to the documentation page, if configured to."""
|
||||
@@ -509,7 +480,7 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
return redirect(
|
||||
get_documentation_url(
|
||||
LibraryVersion.objects.get(
|
||||
library__slug=self.kwargs.get("slug"),
|
||||
library__slug=self.kwargs.get("library_slug"),
|
||||
version=self.get_version(),
|
||||
),
|
||||
latest=True,
|
||||
@@ -517,44 +488,6 @@ class LibraryDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
)
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
set_selected_boost_version(
|
||||
kwargs.get("version_slug", LATEST_RELEASE_URL_PATH_STR), response
|
||||
self.kwargs.get("version_slug", LATEST_RELEASE_URL_PATH_STR), response
|
||||
)
|
||||
return response
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""User has submitted a form and will be redirected to the right record."""
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if form.is_valid():
|
||||
version = form.cleaned_data["version"]
|
||||
return redirect(
|
||||
"library-detail-by-version",
|
||||
version_slug=version.slug,
|
||||
slug=self.object.slug,
|
||||
)
|
||||
else:
|
||||
logger.info("library_list_invalid_version")
|
||||
return redirect(request.get_full_path())
|
||||
return super().get(request)
|
||||
|
||||
def render_to_response(self, context):
|
||||
if self.object.slug != self.kwargs["slug"]:
|
||||
# redirect to canonical case
|
||||
try:
|
||||
url = urls.reverse(
|
||||
"library-detail-by-version",
|
||||
kwargs={
|
||||
"slug": self.object.slug,
|
||||
"version_slug": self.kwargs["version_slug"],
|
||||
},
|
||||
)
|
||||
except KeyError:
|
||||
url = urls.reverse(
|
||||
"library-detail",
|
||||
kwargs={
|
||||
"slug": self.object.slug,
|
||||
},
|
||||
)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
return super().render_to_response(context)
|
||||
|
||||
@@ -186,21 +186,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
const versionResetForm = (event) => {
|
||||
if (event.persisted) {
|
||||
const form = document.getElementById('id_version').closest('form');
|
||||
if (form) {
|
||||
form.reset();
|
||||
const changeVersionAndCategory = (event) => {
|
||||
const urlPatterns = {
|
||||
libraries: {
|
||||
regex: /^(https?:\/\/[\S:]+\/libraries\/)([^\/]+)(\/?[a-z]*)(\/?\S*)?\/?$/,
|
||||
substitution: (event) => {
|
||||
switch (event.target.id) {
|
||||
case 'id_category':
|
||||
return `$1$2$3${event.target.value ? `/${event.target.value}` : ''}/`;
|
||||
case 'id_version':
|
||||
return `$1${event.target.value}$3$4`;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
library: {
|
||||
regex: /^(https?:\/\/[\S:]+\/library\/)([^\/]+)\/([^\/]+)\/?$/,
|
||||
substitution: (event) => `$1${event.target.value}/$3/`
|
||||
},
|
||||
releases: {
|
||||
regex: /^(https?:\/\/[\S:]+\/releases\/)([^\/]+)?\/?$/,
|
||||
substitution: (event) => `$1${event.target.value}/`
|
||||
}
|
||||
};
|
||||
|
||||
const currentUrl = window.location.href;
|
||||
for (const key in urlPatterns) {
|
||||
const pattern = urlPatterns[key];
|
||||
if (pattern.regex.test(currentUrl)) {
|
||||
const substitutionString = pattern.substitution(event);
|
||||
if (substitutionString) {
|
||||
window.location.href = currentUrl.replace(pattern.regex, substitutionString);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
// resets the form on back button press
|
||||
document.getElementById("id_category")?.closest('form').reset();
|
||||
document.getElementById("id_version")?.closest('form').reset();
|
||||
document.getElementById("id_category")?.addEventListener("change", changeVersionAndCategory);
|
||||
document.getElementById("id_version")?.addEventListener("change", changeVersionAndCategory);
|
||||
});
|
||||
(async () => {
|
||||
await trackLoginUpdateCheck();
|
||||
await delay(messageVisibilitySeconds * 1000);
|
||||
await hideMessage();
|
||||
})();
|
||||
window.addEventListener('pageshow', versionResetForm);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
All Libraries <i class="fas fa-chevron-right text-sky-600 dark:text-sky-300 group-hover:text-orange dark:group-hover:text-orange"></i>
|
||||
</a>
|
||||
</div>
|
||||
<h3 class="pb-2 mb-4 text-lg md:text-2xl lg:text-3xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' slug=featured_library.slug %}" class="link-header">{{ featured_library.name }}</a></h3>
|
||||
<h3 class="pb-2 mb-4 text-lg md:text-2xl lg:text-3xl capitalize border-b border-gray-400 text-orange dark:border-slate"><a href="{% url 'library-detail' library_slug=featured_library.slug version_slug=LATEST_RELEASE_URL_PATH_STR %}" class="link-header">{{ featured_library.name }}</a></h3>
|
||||
<span class="pb-1 mx-auto w-full text-sm md:text-base align-left">
|
||||
{{ featured_library.description }}
|
||||
</span>
|
||||
|
||||
@@ -390,7 +390,7 @@ html.dark {
|
||||
</div>
|
||||
<div class="right-menubar" x-data="{ 'searchOpen': false }">
|
||||
<span style="position: relative;" x-ref="desktopSearchArea">
|
||||
<i id="gecko-search-button" data-current-boost-version="{{ current_release.stripped_boost_url_slug }}" data-theme-mode="light" data-font-family="sans-serif" class="fas fa-search icon-link"></i>
|
||||
<i id="gecko-search-button" data-current-boost-version="{{ current_version.stripped_boost_url_slug }}" data-theme-mode="light" data-font-family="sans-serif" class="fas fa-search icon-link"></i>
|
||||
<script>
|
||||
const geckoSearchButton = document.getElementById('gecko-search-button');
|
||||
geckoSearchButton.setAttribute('data-theme-mode', localStorage.getItem('colorMode') === 'dark' ? 'dark' : 'light');
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
<tr class="border-0 md:border border-gray-200/10 border-dotted md:border-t-0 md:border-r-0 md:border-l-0 md:border-b-1 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-200 ease-in-out cursor-pointer"
|
||||
onclick="window.location='{% if version_str != LATEST_RELEASE_URL_PATH_STR %}{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}{% else %}{% url 'library-detail' slug=library_version.library.slug %}{% endif %}'">
|
||||
onclick="window.location='{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}'">
|
||||
<td class="py-2 align-top md:w-1/5">
|
||||
<a class="mr-1 font-bold capitalize text-sky-600 dark:text-sky-300 hover:text-orange dark:hover:text-orange"
|
||||
href="
|
||||
{% if version_str != LATEST_RELEASE_URL_PATH_STR %}
|
||||
{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}
|
||||
{% else %}
|
||||
{% url 'library-detail' slug=library_version.library.slug %}
|
||||
{% endif %}"
|
||||
href="{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}"
|
||||
>{{ library_version.library.name }}</a>
|
||||
</td>
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
{% load date_filters %}
|
||||
|
||||
<div class="relative content-between p-3 bg-white md:rounded-lg md:shadow-lg md:p-5 dark:bg-charcoal hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-200 ease-in-out cursor-pointer"
|
||||
onclick="window.location='{% if version_str != LATEST_RELEASE_URL_PATH_STR %}{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}{% else %}{% url 'library-detail' slug=library_version.library.slug %}{% endif %}'">
|
||||
onclick="window.location='{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}'">
|
||||
<div class="">
|
||||
<h3 class="pb-2 text-xl md:text-2xl capitalize border-b border-gray-700">
|
||||
<a class="link-header" href="
|
||||
{% if version_str != LATEST_RELEASE_URL_PATH_STR %}
|
||||
{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}
|
||||
{% else %}
|
||||
{% url 'library-detail' slug=library_version.library.slug %}
|
||||
{% endif %}"
|
||||
>{{ library_version.library.name }}</a>
|
||||
{% for author in library_version.library.authors.all %}
|
||||
<a class="link-header" href="{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}">{{ library_version.library.name }}</a>
|
||||
{% for author in library.authors.all %}
|
||||
{% if author.image %}
|
||||
<img src="{{ author.image.url }}" class="inline float-right rounded w-[30px] ml-1" alt="{{ author.get_display_name }}" />
|
||||
{% endif %}
|
||||
@@ -34,7 +28,7 @@ onclick="window.location='{% if version_str != LATEST_RELEASE_URL_PATH_STR %}{%
|
||||
{# <div class="w-1/6 tracking-wider text-charcoal dark:text-white/60">{% if library_version.library.first_boost_version %}{{ library_version.library.first_boost_version.release_date|years_since }} yrs{% endif %}</div>#}
|
||||
<div class="w-5/6 text-right mr-2 font-bold capitalize text-sky-600 dark:text-sky-300">
|
||||
{% for c in library_version.library.categories.all %}
|
||||
<a href="{% url 'libraries' %}?category={{ c.slug }}{% if version_str != LATEST_RELEASE_URL_PATH_STR %}&version={{ version.slug }}{% endif %}" class="hover:text-orange">{{ c.name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
<a href="{% url 'libraries-list' library_view_str='grid' category_slug=c.slug version_slug=version_str %}" class="hover:text-orange">{{ c.name }}</a>{% if not forloop.last %}, {% endif %}{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,14 +1,8 @@
|
||||
<tr class="border-0 md:border border-gray-200/10 border-dotted md:border-t-0 md:border-r-0 md:border-l-0 md:border-b-1 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all duration-300 ease-in-out cursor-pointer"
|
||||
onclick="window.location='{% if version %}{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}{% else %}{% url 'library-detail' slug=library_version.library.slug %}{% endif %}'">
|
||||
onclick="window.location='{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}'">
|
||||
<td class="py-2 align-top md:w-1/5">
|
||||
<a class="mr-1 pl-1 font-bold capitalize text-sky-600 dark:text-sky-300 hover:text-orange dark:hover:text-orange"
|
||||
href="
|
||||
{% if version_str != LATEST_RELEASE_URL_PATH_STR %}
|
||||
{% url 'library-detail-by-version' slug=library_version.library.slug version_slug=version.slug %}
|
||||
{% else %}
|
||||
{% url 'library-detail' slug=library_version.library.slug %}
|
||||
{% endif %}"
|
||||
>{{ library_version.library.name }}</a>
|
||||
href="{% url 'library-detail' library_slug=library_version.library.slug version_slug=version_str %}">{{ library_version.library.name }}</a>
|
||||
</td>
|
||||
|
||||
<td class="py-2 px-2 align-top w-12">
|
||||
@@ -21,7 +21,7 @@
|
||||
<table class="table-auto w-full">
|
||||
<tbody>
|
||||
{% for library_version in result.library_version_list %}
|
||||
{% include "libraries/_library_category_list_item.html" %}
|
||||
{% include "libraries/_library_categorized_list_item.html" %}
|
||||
{% empty %}
|
||||
<p class="text-gray-600 dark:text-gray-400">No libraries in this category yet.</p>
|
||||
{% endfor %}
|
||||
@@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n static avatar_tags %}
|
||||
{% load i18n static avatar_tags version_select %}
|
||||
|
||||
{% block title %}{{ object.display_name }} ({{ version.display_name }}){% endblock %}
|
||||
{% block description %}{% if library_version.description %}{% trans library_version.description %}{% endif %}{% endblock %}
|
||||
@@ -17,21 +17,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink mr-3 md:mr-0">
|
||||
<form action="{% url 'library-detail' slug=object.slug %}"
|
||||
method="post">
|
||||
<div>
|
||||
<label for="id_version" hidden="true">Versions:</label>
|
||||
<select onchange="this.form.submit()"
|
||||
name="version"
|
||||
class="dropdown pb-0"
|
||||
id="id_version">
|
||||
<option value="{{ LATEST_RELEASE_URL_PATH_STR }}" {% if version_str == LATEST_RELEASE_URL_PATH_NAME %}selected="selected"{% endif %}>Latest</option>
|
||||
{% for v in versions %}
|
||||
<option value="{{ v.pk }}" {% if version_str == v.slug %}selected="selected"{% endif %}>{{ v.display_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
{% version_select %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +45,7 @@
|
||||
<span class="font-bold">Categories:</span>
|
||||
{% for category in object.categories.all %}
|
||||
<a class="inline text-sky-600 dark:text-sky-300 hover:text-orange dark:hover:text-orange"
|
||||
href="{% url 'libraries' %}?category={{ category.slug }}{% if version_str != LATEST_RELEASE_URL_PATH_NAME %}&version={{ version.slug }}{% endif %}">{{ category.name }}</a>
|
||||
href="{% url 'libraries-list' category_slug=category.slug library_view_str=library_view_str version_slug=version_str %}">{{ category.name }}</a>
|
||||
{% if not forloop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
43
templates/libraries/grid_list.html
Normal file
43
templates/libraries/grid_list.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans "Boost Libraries" %}{% endblock %}
|
||||
{% block description %}{% trans "Explore our comprehensive list of Boost C++ Libraries and discover tools for multithreading, image processing, testing, and more." %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="content">
|
||||
{% include "libraries/includes/library_preferences.html" %}
|
||||
{% if object_list %}
|
||||
{# alert for non-current Boost versions #}
|
||||
{% include "libraries/includes/version_alert.html" %}
|
||||
|
||||
{# Libraries list #}
|
||||
<div class="grid grid-cols-1 gap-4 mb-5 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for library_version in object_list %}
|
||||
{% include "libraries/_library_grid_list_item.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{# end libraries list #}
|
||||
|
||||
{% if page_obj.paginator %}
|
||||
{# pagination #}
|
||||
<div class="space-x-3 text-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page=1" class="text-orange"><small> << First</small></a>
|
||||
<a href="?page={{ page_obj.previous_page_number }}" class="text-orange"><small> < Previous</small> </a>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}" class="text-orange"><small>Next <small> > </small></a>
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}" class="text-orange">Last <small> >></small></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{# end pagination #}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div>
|
||||
No library records available at this time. Check back later.
|
||||
</div>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -1,4 +1,4 @@
|
||||
{% load version_select %}
|
||||
{% load version_select %}
|
||||
{% with request.resolver_match.view_name as view_name %}
|
||||
<div class="pt-3 px-0 mb-2 text-right md:mb-2 mx-3 md:mx-0">
|
||||
<form action="{{request.path}}" method="get">
|
||||
@@ -10,15 +10,15 @@
|
||||
{# Display options #}
|
||||
<div class="flex space-x-3">
|
||||
<div class="relative group">
|
||||
<a title="Name View" href="{% url 'libraries-mini' %}{% if url_params %}?{{ request.GET.urlencode }}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-list p-[10px] {% if view_name == 'libraries-mini' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<a title="Name View" href="{% if category_slug %}{% url 'libraries-list' library_view_str='list' version_slug=version_str category_slug=category_slug %}{% else %}{% url 'libraries-list' library_view_str='list' version_slug=version_str %}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-list p-[10px] {% if library_view_str == 'list' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<span class="z-50 group-hover:opacity-100 transition-opacity bg-slate px-1 text-xs text-gray-100 rounded-sm absolute top-5 left-1/2 -translate-x-1/2 translate-y-full opacity-0 m-0 mx-auto w-auto">List View</span>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<a title="Grid View" href="{% url 'libraries-grid' %}{% if url_params %}?{{ request.GET.urlencode }}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-th-large p-[10px] {% if view_name == 'libraries-grid' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<a title="Grid View" href="{% if category_slug %}{% url 'libraries-list' library_view_str='grid' version_slug=version_str category_slug=category_slug %}{% else %}{% url 'libraries-list' library_view_str='grid' version_slug=version_str %}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-th-large p-[10px] {% if library_view_str == 'grid' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<span class="z-50 group-hover:opacity-100 transition-opacity bg-slate px-1 text-xs text-gray-100 rounded-sm absolute top-5 left-1/2 -translate-x-1/2 translate-y-full opacity-0 m-0 mx-auto w-auto">Grid View</span>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<a title="Category View" href="{% url 'libraries-by-category' %}{% if url_params %}?{{ request.GET.urlencode }}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-cat p-[10px] {% if view_name == 'libraries-by-category' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<a title="Category View" href="{% if category_slug %}{% url 'libraries-list' library_view_str='categorized' version_slug=version_str category_slug=category_slug %}{% else %}{% url 'libraries-list' library_view_str='categorized' version_slug=version_str %}{% endif %}"><i class="link rounded border border-gray-300 cursor-pointer fas fa-cat p-[10px] {% if library_view_str == 'categorized' %}bg-gray-100 dark:bg-slate{% else %}hover:bg-gray-100 dark:hover:bg-slate{% endif %}"></i></a>
|
||||
<span class="z-50 group-hover:opacity-100 transition-opacity bg-slate px-1 text-xs text-gray-100 rounded-sm absolute top-5 left-1/2 -translate-x-1/2 translate-y-full opacity-0 m-0 mx-auto w-auto">Category View</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,22 +26,20 @@
|
||||
<div></div>
|
||||
|
||||
{# Select a category #}
|
||||
{% if view_name != 'libraries-by-category' %}
|
||||
<div>
|
||||
{# todo: if someone selects a category and hits back, it retains their choice here. #}
|
||||
<select onchange="this.form.submit()"
|
||||
<div>
|
||||
<select
|
||||
name="category"
|
||||
class="block py-2 pr-11 pl-5 mb-3 w-full text-sm bg-white rounded-md border border-gray-300 cursor-pointer sm:inline-block md:mb-0 ml-3 md:ml-0 md:w-auto dark:bg-black dark:border-slate"
|
||||
class="block py-2 pr-11 pl-5 mb-3 w-full text-sm bg-white rounded-md border border-gray-300 cursor-pointer sm:inline-block md:mb-0 ml-3 md:ml-0 md:w-auto dark:bg-black dark:border-slate disabled:dark:"
|
||||
id="id_category"
|
||||
{% if library_view_str == 'categorized' %}disabled="disabled"{% endif %}
|
||||
>
|
||||
|
||||
<option value="">Filter by category</option>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.slug }}" {% if category == c %}selected="selected"{% endif %}>{{ c.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Select a version #}
|
||||
<div class="flex grow justify-end">
|
||||
{% version_select %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% for library in libraries %}
|
||||
<div class="bg-white dark:bg-gray-800 flex py-2 px-3 cursor-pointer select-none group" @click="document.location.href = '{% url 'library-detail' slug=library.slug %}';">
|
||||
<a href="{% url 'library-detail' slug=library.slug %}" class="group-hover:text-orange">
|
||||
<div class="bg-white dark:bg-gray-800 flex py-2 px-3 cursor-pointer select-none group" @click="document.location.href = '{% url 'library-detail' library_slug=library.slug version_slug='latest' %}';">
|
||||
<a href="{% url 'library-detail' library_slug=library.slug version_slug='latest' %}" class="group-hover:text-orange">
|
||||
<span class="">
|
||||
{{ library.name|capfirst }}
|
||||
</span>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
<div role="alert" class="py-2 px-3 mb-3 text-center rounded-sm bg-yellow-200/70">
|
||||
<p class="p-0 m-0">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
{% if version == current_release %}
|
||||
You've currently chosen the {{ current_release.display_name }} version. If a newer release comes out, you will continue to view the {{ current_release.display_name }} version, not the new <a href="{{ version_alert_url }}" class="font-semibold underline dark:text-white text-charcoal">latest release</a>.
|
||||
{% if selected_version == current_version %}
|
||||
You've currently chosen the {{ current_version.display_name }} version. If a newer release comes out, you will continue to view the {{ current_version.display_name }} version, not the new <a href="{{ version_alert_url }}" class="font-semibold underline dark:text-white text-charcoal">latest release</a>.
|
||||
{% else %}
|
||||
{% if version.beta %}
|
||||
{% if selected_version.beta %}
|
||||
This is a beta version of Boost.
|
||||
{% elif version.full_release %}
|
||||
This is an older version and was released in {{ version.release_date|date:"Y"}}.
|
||||
This is an older version and was released in {{ selected_version.release_date|date:"Y"}}.
|
||||
{% else %}
|
||||
This version of Boost is under active development.
|
||||
{% endif %}
|
||||
The <a href="{{ version_alert_url }}" class="font-semibold underline dark:text-white text-charcoal">current version</a> is {{ current_release.display_name }}.
|
||||
The <a href="{{ version_alert_url }}" class="font-semibold underline dark:text-white text-charcoal">current version</a> is {{ current_version.display_name }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans "Boost Libraries" %}{% endblock %}
|
||||
{% block description %}{% trans "Explore our comprehensive list of Boost C++ Libraries and discover tools for multithreading, image processing, testing, and more." %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="content">
|
||||
{% if library_list %}
|
||||
{% include "libraries/includes/library_preferences.html" %}
|
||||
|
||||
{# alert for non-current Boost versions #}
|
||||
{% include "libraries/includes/version_alert.html" %}
|
||||
|
||||
{# Libraries list #}
|
||||
<div class="grid grid-cols-1 gap-4 mb-5 md:grid-cols-2 lg:grid-cols-3">
|
||||
{% for library_version in library_version_list %}
|
||||
{% include "libraries/_library_list_item.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{# end libraries list #}
|
||||
|
||||
{% if page_obj.paginator %}
|
||||
{# pagination #}
|
||||
<div class="space-x-3 text-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<a href="?page=1" class="text-orange"><small> << First</small></a>
|
||||
<a href="?page={{ page_obj.previous_page_number }}" class="text-orange"><small> < Previous</small> </a>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<a href="?page={{ page_obj.next_page_number }}" class="text-orange"><small>Next <small> > </small></a>
|
||||
<a href="?page={{ page_obj.paginator.num_pages }}" class="text-orange">Last <small> >></small></a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{# end pagination #}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
@@ -20,8 +20,8 @@
|
||||
{% endif %}
|
||||
<table class="table-auto w-full">
|
||||
<tbody>
|
||||
{% for library_version in library_version_list %}
|
||||
{% include "libraries/_library_flat_list_item.html" %}
|
||||
{% for library_version in object_list %}
|
||||
{% include "libraries/_library_vertical_list_item.html" %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -1,15 +1,18 @@
|
||||
{% load boost_version %}
|
||||
<form action="." method="get">
|
||||
<select onchange="this.form.submit()"
|
||||
name="version"
|
||||
class="dropdown !mb-0 h-[38px]"
|
||||
id="id_version">
|
||||
<select
|
||||
name="version"
|
||||
class="dropdown !mb-0 h-[38px]"
|
||||
id="id_version"
|
||||
>
|
||||
<option value="{{ LATEST_RELEASE_URL_PATH_STR }}"
|
||||
{% if version_str == LATEST_RELEASE_URL_PATH_STR %}selected="selected"{% endif %}>
|
||||
Latest
|
||||
</option>
|
||||
{% for v in versions %}
|
||||
<option value="{{ v.slug }}"
|
||||
{% if version_str == v.slug %}selected="selected"{% endif %}>
|
||||
<option value="{{ v.slug|boost_version }}"
|
||||
{% if version_str == v.slug %}selected="selected"{% endif %}
|
||||
>
|
||||
{{ v.display_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
{% load static %}
|
||||
{% load text_helpers %}
|
||||
{% load avatar_tags %}
|
||||
{% load version_select %}
|
||||
{% block title %}{% blocktrans with version_name=version.display_name %}Boost {{ version_name }}{% endblocktrans %}{% endblock %}
|
||||
{% block description %}{% blocktrans with version_name=version.display_name %}Discover what's new in Boost {{ version_name }}{% endblocktrans %}{% endblock %}
|
||||
{% block content %}
|
||||
<main class="content">
|
||||
{% if version %}
|
||||
{% if selected_version %}
|
||||
<div class="py-3 px-3 md:mt-3 md:px-0 mb-0 w-full flex flex-row flex-nowrap items-center"
|
||||
x-data="{'showSearch': false}"
|
||||
x-on:keydown.escape="showSearch=false">
|
||||
@@ -20,22 +21,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink text-right">
|
||||
<form action="." method="post">
|
||||
<div>
|
||||
<select onchange="this.form.submit()"
|
||||
name="version"
|
||||
class="dropdown !mb-0 h-[38px]"
|
||||
id="id_version">
|
||||
<option value="{{ LATEST_RELEASE_URL_PATH_STR }}" {% if version_str == LATEST_RELEASE_URL_PATH_STR %}selected="selected"{% endif %}>Latest</option>
|
||||
{% for v in versions %}
|
||||
<option value="{{ v.pk }}"
|
||||
{% if version_str == v.slug %}selected="selected"{% endif %}>
|
||||
{{ v.display_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
{% version_select %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- alert for non-current Boost versions -->
|
||||
|
||||
29
versions/converters.py
Normal file
29
versions/converters.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from libraries.constants import (
|
||||
LATEST_RELEASE_URL_PATH_STR,
|
||||
LEGACY_LATEST_RELEASE_URL_PATH_STR,
|
||||
VERSION_SLUG_PREFIX,
|
||||
)
|
||||
|
||||
|
||||
def to_python(value):
|
||||
if value in (LATEST_RELEASE_URL_PATH_STR, LEGACY_LATEST_RELEASE_URL_PATH_STR):
|
||||
return LATEST_RELEASE_URL_PATH_STR
|
||||
return f"{VERSION_SLUG_PREFIX}{value.replace('.', '-')}"
|
||||
|
||||
|
||||
def to_url(value):
|
||||
if value == LATEST_RELEASE_URL_PATH_STR:
|
||||
return LATEST_RELEASE_URL_PATH_STR
|
||||
if value:
|
||||
value = value.replace(VERSION_SLUG_PREFIX, "").replace("-", ".")
|
||||
return value
|
||||
|
||||
|
||||
class BoostVersionSlugConverter:
|
||||
regex = r"[a-zA-Z0-9\-\.]+"
|
||||
|
||||
def to_python(self, value):
|
||||
return to_python(value)
|
||||
|
||||
def to_url(self, value):
|
||||
return to_url(value)
|
||||
@@ -5,6 +5,7 @@ from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.text import slugify
|
||||
|
||||
from .converters import to_url
|
||||
from .managers import VersionManager, VersionFileManager
|
||||
|
||||
User = get_user_model()
|
||||
@@ -53,7 +54,7 @@ class Version(models.Model):
|
||||
return super(Version, self).save(*args, **kwargs)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("release-detail", args=[str(self.slug)])
|
||||
return reverse("release-detail", args=[to_url(str(self.slug))])
|
||||
|
||||
def get_slug(self):
|
||||
if self.slug:
|
||||
|
||||
0
versions/templatetags/__init__.py
Normal file
0
versions/templatetags/__init__.py
Normal file
8
versions/templatetags/boost_version.py
Normal file
8
versions/templatetags/boost_version.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def boost_version(slug):
|
||||
return slug.replace("boost-", "").replace("-", ".")
|
||||
@@ -111,7 +111,7 @@ def test_stripped_boost_url_slug(slug, expected, version):
|
||||
|
||||
|
||||
def test_get_absolute_url(version):
|
||||
expected_url = f"/releases/{version.slug}/"
|
||||
expected_url = f"/releases/{version.slug.replace('boost-', '').replace('-', '.')}/"
|
||||
assert version.get_absolute_url() == expected_url
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ def test_version_most_recent_detail(version, tp):
|
||||
|
||||
ten_years_ago = now - timedelta(days=365 * 10)
|
||||
baker.make("versions.Version", name="boost-0.0.0", release_date=ten_years_ago)
|
||||
res = tp.get_check_200("releases-most-recent")
|
||||
res = tp.get_check_200("releases-most-recent", follow=True)
|
||||
assert "versions" in res.context
|
||||
assert res.context["version"] == version
|
||||
|
||||
@@ -22,20 +22,12 @@ def test_version_detail_no_data(tp):
|
||||
GET /releases/
|
||||
"""
|
||||
Version.objects.all().delete()
|
||||
tp.get_check_200("releases-most-recent")
|
||||
tp.get_check_200("releases-most-recent", follow=True)
|
||||
|
||||
|
||||
def test_version_detail(version, tp):
|
||||
"""
|
||||
GET /releases/{slug}/
|
||||
GET /releases/{version_slug}/
|
||||
"""
|
||||
res = tp.get("release-detail", slug=version.slug)
|
||||
tp.response_200(res)
|
||||
|
||||
|
||||
def test_version_detail_post(version, tp):
|
||||
"""
|
||||
POST /releases/{slug}/
|
||||
"""
|
||||
res = tp.post("releases-most-recent", data={"version": version.slug})
|
||||
res = tp.get("release-detail", version_slug=version.slug)
|
||||
tp.response_200(res)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.db.models.query import QuerySet
|
||||
import structlog
|
||||
from itertools import groupby
|
||||
from operator import attrgetter
|
||||
|
||||
@@ -15,7 +14,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from core.models import RenderedContent
|
||||
from libraries.constants import LATEST_RELEASE_URL_PATH_STR
|
||||
from libraries.forms import VersionSelectionForm
|
||||
from libraries.mixins import VersionAlertMixin
|
||||
from libraries.mixins import VersionAlertMixin, BoostVersionMixin
|
||||
from libraries.models import Commit, CommitAuthor
|
||||
from libraries.utils import (
|
||||
set_selected_boost_version,
|
||||
@@ -25,11 +24,8 @@ from libraries.utils import (
|
||||
from versions.models import Review, Version
|
||||
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class VersionDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
class VersionDetail(FormMixin, BoostVersionMixin, VersionAlertMixin, DetailView):
|
||||
"""Web display of list of Versions"""
|
||||
|
||||
form_class = VersionSelectionForm
|
||||
@@ -39,7 +35,8 @@ class VersionDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data()
|
||||
obj = self.get_object()
|
||||
# .get_object() is called on /releases, with no version pk nor existing context
|
||||
obj = context.get("selected_version") or self.get_object()
|
||||
|
||||
# Handle the case where no data has been uploaded
|
||||
if not obj:
|
||||
@@ -50,18 +47,18 @@ class VersionDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
)
|
||||
context["versions"] = None
|
||||
context["downloads"] = None
|
||||
context["current_release"] = None
|
||||
context["selected_version"] = None
|
||||
context["is_current_release"] = False
|
||||
return context
|
||||
|
||||
context["versions"] = Version.objects.version_dropdown_strict()
|
||||
downloads = obj.downloads.all().order_by("operating_system")
|
||||
context["downloads"] = {
|
||||
k: list(v)
|
||||
for k, v in groupby(downloads, key=attrgetter("operating_system"))
|
||||
}
|
||||
obj = self.get_object()
|
||||
context["heading"] = self.get_version_heading(
|
||||
obj, context["current_release"] == obj
|
||||
obj, context["current_version"] == obj
|
||||
)
|
||||
context["release_notes"] = self.get_release_notes(obj)
|
||||
context["top_contributors_release"] = self.get_top_contributors_release(obj)
|
||||
@@ -105,53 +102,26 @@ class VersionDetail(FormMixin, VersionAlertMixin, DetailView):
|
||||
else:
|
||||
return "Development Branch"
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""User has submitted a form and will be redirected to the right record."""
|
||||
form = self.get_form()
|
||||
version_slug = self.request.POST.get("version")
|
||||
if version_slug == LATEST_RELEASE_URL_PATH_STR:
|
||||
response = redirect("releases-most-recent")
|
||||
set_selected_boost_version(LATEST_RELEASE_URL_PATH_STR, response)
|
||||
return response
|
||||
elif form.is_valid():
|
||||
version = form.cleaned_data["version"]
|
||||
response = redirect(
|
||||
"release-detail",
|
||||
slug=version.slug,
|
||||
)
|
||||
set_selected_boost_version(version.slug, response)
|
||||
return response
|
||||
else:
|
||||
logger.info("version_detail_invalid_version")
|
||||
return super().get(request)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
response = super().dispatch(request, *args, **kwargs)
|
||||
|
||||
# if 'release' clear the version values, e.g. from version_alert
|
||||
if self.kwargs.get("slug") == LATEST_RELEASE_URL_PATH_STR:
|
||||
response = redirect("releases-most-recent")
|
||||
set_selected_boost_version(LATEST_RELEASE_URL_PATH_STR, response)
|
||||
return response
|
||||
|
||||
version = determine_selected_boost_version(
|
||||
self.kwargs.get("slug"), self.request
|
||||
)
|
||||
if version != self.kwargs.get("slug"):
|
||||
version_slug = self.kwargs.get("version_slug")
|
||||
# if set in kwargs, update the cookie
|
||||
if version_slug:
|
||||
set_selected_boost_version(version_slug, response)
|
||||
else:
|
||||
version_slug = (
|
||||
determine_selected_boost_version(version_slug, self.request)
|
||||
or LATEST_RELEASE_URL_PATH_STR
|
||||
)
|
||||
response = redirect(
|
||||
"release-detail",
|
||||
slug=version,
|
||||
version_slug=version_slug,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""Return the object that the view is displaying"""
|
||||
if self.request.POST:
|
||||
version_slug = self.request.POST.get("version")
|
||||
else:
|
||||
version_slug = self.kwargs.get("slug", LATEST_RELEASE_URL_PATH_STR)
|
||||
|
||||
version_slug = self.kwargs.get("version_slug", LATEST_RELEASE_URL_PATH_STR)
|
||||
if version_slug == LATEST_RELEASE_URL_PATH_STR:
|
||||
return Version.objects.most_recent()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user