From ed41a4fe1933e13d19a4e648d63011b3d2a67cc5 Mon Sep 17 00:00:00 2001 From: Kevin Morris Date: Wed, 30 Mar 2022 16:16:47 -0700 Subject: [PATCH] feat: add paging to package depends & required by This patch does not include a javascript implementating, but provides a pure HTML/HTTP method of paging through these lists. Also fixes erroneous limiting. We now use a hardcoded limit of 20 by default. Signed-off-by: Kevin Morris --- aurweb/packages/util.py | 6 +-- aurweb/pkgbase/util.py | 13 ++++- aurweb/routers/packages.py | 51 +++++++++++++++---- aurweb/templates.py | 2 + po/aurweb.pot | 8 +++ .../partials/packages/package_metadata.html | 16 +++++- test/test_packages_routes.py | 45 ++++++++++++++++ 7 files changed, 125 insertions(+), 16 deletions(-) diff --git a/aurweb/packages/util.py b/aurweb/packages/util.py index e8569f29..5085ddf4 100644 --- a/aurweb/packages/util.py +++ b/aurweb/packages/util.py @@ -214,7 +214,7 @@ def query_notified(query: List[models.Package], return output -def pkg_required(pkgname: str, provides: List[str], limit: int) \ +def pkg_required(pkgname: str, provides: List[str]) \ -> List[PackageDependency]: """ Get dependencies that match a string in `[pkgname] + provides`. @@ -227,8 +227,8 @@ def pkg_required(pkgname: str, provides: List[str], limit: int) \ targets = set([pkgname] + provides) query = db.query(PackageDependency).join(Package).filter( PackageDependency.DepName.in_(targets) - ).order_by(Package.Name.asc()).limit(limit) - return query.all() + ).order_by(Package.Name.asc()) + return query @register_filter("source_uri") diff --git a/aurweb/pkgbase/util.py b/aurweb/pkgbase/util.py index 18af3df0..ea952dce 100644 --- a/aurweb/pkgbase/util.py +++ b/aurweb/pkgbase/util.py @@ -11,16 +11,25 @@ from aurweb.models.package_request import PENDING_ID, PackageRequest from aurweb.models.package_vote import PackageVote from aurweb.scripts import notify from aurweb.templates import make_context as _make_context +from aurweb.templates import make_variable_context as _make_variable_context -def make_context(request: Request, pkgbase: PackageBase) -> Dict[str, Any]: +async def make_variable_context(request: Request, pkgbase: PackageBase) \ + -> Dict[str, Any]: + ctx = await _make_variable_context(request, pkgbase.Name) + return make_context(request, pkgbase, ctx) + + +def make_context(request: Request, pkgbase: PackageBase, + context: Dict[str, Any] = None) -> Dict[str, Any]: """ Make a basic context for package or pkgbase. :param request: FastAPI request :param pkgbase: PackageBase instance :return: A pkgbase context without specific differences """ - context = _make_context(request, pkgbase.Name) + if not context: + context = _make_context(request, pkgbase.Name) context["git_clone_uri_anon"] = config.get("options", "git_clone_uri_anon") context["git_clone_uri_priv"] = config.get("options", "git_clone_uri_priv") diff --git a/aurweb/routers/packages.py b/aurweb/routers/packages.py index bc12455d..f14b0ad8 100644 --- a/aurweb/routers/packages.py +++ b/aurweb/routers/packages.py @@ -2,7 +2,7 @@ from collections import defaultdict from http import HTTPStatus from typing import Any, Dict, List -from fastapi import APIRouter, Form, Request, Response +from fastapi import APIRouter, Form, Query, Request, Response import aurweb.filters # noqa: F401 @@ -33,7 +33,7 @@ async def packages_get(request: Request, context: Dict[str, Any], context["O"] = offset # Limit PP to options.max_search_results - max_search_results = aurweb.config.getint("options", "max_search_results") + max_search_results = config.getint("options", "max_search_results") context["PP"] = per_page = min(per_page, max_search_results) # Query search by. @@ -123,7 +123,22 @@ async def packages(request: Request) -> Response: @router.get("/packages/{name}") -async def package(request: Request, name: str) -> Response: +async def package(request: Request, name: str, + all_deps: bool = Query(default=False), + all_reqs: bool = Query(default=False)) -> Response: + """ + Get a package by name. + + By default, we limit the number of depends and requires results + to 20. To bypass this and load all of them, which should be triggered + via a "Show more" link near the limited listing. + + :param name: Package.Name + :param all_deps: Boolean indicating whether we should load all depends + :param all_reqs: Boolean indicating whether we should load all requires + :return: FastAPI Response + """ + # Get the Package. pkg = get_pkg_or_base(name, models.Package) pkgbase = pkg.PackageBase @@ -139,23 +154,41 @@ async def package(request: Request, name: str) -> Response: rels_data["r"].append(rel) # Add our base information. - context = pkgbaseutil.make_context(request, pkgbase) + context = await pkgbaseutil.make_variable_context(request, pkgbase) + + context.update( + { + "all_deps": all_deps, + "all_reqs": all_reqs + } + ) + context["package"] = pkg # Package sources. context["sources"] = pkg.package_sources.order_by( models.PackageSource.Source.asc()).all() + # Listing metadata. + context["max_listing"] = max_listing = 20 + # Package dependencies. - max_depends = config.getint("options", "max_depends") - context["dependencies"] = pkg.package_dependencies.order_by( + deps = pkg.package_dependencies.order_by( models.PackageDependency.DepTypeID.asc(), models.PackageDependency.DepName.asc() - ).limit(max_depends).all() + ) + context["depends_count"] = deps.count() + if not all_deps: + deps = deps.limit(max_listing) + context["dependencies"] = deps.all() # Package requirements (other packages depend on this one). - context["required_by"] = pkgutil.pkg_required( - pkg.Name, [p.RelName for p in rels_data.get("p", [])], max_depends) + reqs = pkgutil.pkg_required( + pkg.Name, [p.RelName for p in rels_data.get("p", [])]) + context["reqs_count"] = reqs.count() + if not all_reqs: + reqs = reqs.limit(max_listing) + context["required_by"] = reqs.all() context["licenses"] = pkg.package_licenses diff --git a/aurweb/templates.py b/aurweb/templates.py index ccadb16d..6520bedf 100644 --- a/aurweb/templates.py +++ b/aurweb/templates.py @@ -100,6 +100,8 @@ async def make_variable_context(request: Request, title: str, next: str = None): for k, v in to_copy.items(): context[k] = v + context["q"] = dict(request.query_params) + return context diff --git a/po/aurweb.pot b/po/aurweb.pot index e7c632e3..bc4bab84 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -2338,3 +2338,11 @@ msgstr "" #: templates/partials/tu/proposal/details.html msgid "assigned" msgstr "" + +#: templaets/partials/packages/package_metadata.html +msgid "Show %d more" +msgstr "" + +#: templates/partials/packages/package_metadata.html +msgid "dependencies" +msgstr "" diff --git a/templates/partials/packages/package_metadata.html b/templates/partials/packages/package_metadata.html index 6f58c2be..123b994d 100644 --- a/templates/partials/packages/package_metadata.html +++ b/templates/partials/packages/package_metadata.html @@ -1,5 +1,5 @@
-

{{ "Dependencies" | tr }} ({{ dependencies | length }})

+

{{ "Dependencies" | tr }} ({{ depends_count }})

-

{{ "Required by" | tr }} ({{ required_by | length }})

+

{{ "Required by" | tr }} ({{ reqs_count }})

diff --git a/test/test_packages_routes.py b/test/test_packages_routes.py index ee837912..e4c992af 100644 --- a/test/test_packages_routes.py +++ b/test/test_packages_routes.py @@ -276,6 +276,51 @@ def test_package(client: TestClient, package: Package): assert conflicts[0].text.strip() == ", ".join(expected) +def paged_depends_required(client: TestClient, package: Package): + maint = package.PackageBase.Maintainer + new_pkgs = [] + + with db.begin(): + # Create 25 new packages that'll be used to depend on our package. + for i in range(26): + base = db.create(PackageBase, Name=f"new_pkg{i}", Maintainer=maint) + new_pkgs.append(db.create(Package, Name=base.Name)) + + # Create 25 deps. + for i in range(25): + create_package_dep(package, f"dep_{i}") + + with db.begin(): + # Create depends on this package so we get some required by listings. + for new_pkg in new_pkgs: + create_package_dep(new_pkg, package.Name) + + with client as request: + resp = request.get(package_endpoint(package)) + assert resp.status_code == int(HTTPStatus.OK) + + # Test depends show link. + assert "Show 5 more" in resp.text + + # Test required by show more link, we added 26 packages. + assert "Show 6 more" in resp.text + + # Follow both links at the same time. + with client as request: + resp = request.get( + package_endpoint(package), + params={ + "all_deps": True, + "all_reqs": True, + } + ) + assert resp.status_code == int(HTTPStatus.OK) + + # We're should see everything and have no link. + assert "Show 5 more" not in resp.text + assert "Show 6 more" not in resp.text + + def test_package_comments(client: TestClient, user: User, package: Package): now = (time.utcnow()) with db.begin():