- No vulnerability found.
+ No vulnerability found.
{% endfor %}
@@ -81,19 +77,10 @@
-
- {% if is_paginated %}
- {% include 'includes/pagination.html' with page_obj=page_obj %}
- {% endif %}
+ {% if is_paginated %}
+ {% include 'includes/pagination.html' with page_obj=page_obj %}
+ {% endif %}
{% endif %}
-
-
+
{% endblock %}
\ No newline at end of file
diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py
index 6b03b5c40..e0e8078a1 100644
--- a/vulnerabilities/views.py
+++ b/vulnerabilities/views.py
@@ -3,7 +3,7 @@
# VulnerableCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
-# See https://github.com/aboutcode-org/vulnerablecode for support or download.
+# See https://github.com/nexB/vulnerablecode for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#
import logging
@@ -32,6 +32,7 @@
from vulnerabilities import models
from vulnerabilities.forms import ApiUserCreationForm
from vulnerabilities.forms import PackageSearchForm
+from vulnerabilities.forms import PaginationForm
from vulnerabilities.forms import VulnerabilitySearchForm
from vulnerabilities.models import VulnerabilityStatusType
from vulnerabilities.severity_systems import EPSS
@@ -40,6 +41,7 @@
from vulnerablecode.settings import env
PAGE_SIZE = 20
+MAX_PAGE_SIZE = 100
def purl_sort_key(purl: models.Package):
@@ -65,73 +67,129 @@ def get_purl_version_class(purl: models.Package):
return purl_version_class
-class PackageSearch(ListView):
- model = models.Package
- template_name = "packages.html"
- ordering = ["type", "namespace", "name", "version"]
+class BaseSearchView(ListView):
+ """Base view for implementing search functionality with pagination."""
+
paginate_by = PAGE_SIZE
+ max_page_size = MAX_PAGE_SIZE
- def get_paginate_by(self, queryset):
- page_size = self.request.GET.get("page_size", "")
- return int(page_size) if page_size.isdigit() else self.paginate_by
+ def get_paginate_by(self, queryset=None):
+ """
+ This function would get and validate the page size from request parameters.
+ It returns a page size between 1 and max_page_size, defaulting to
+ self.paginate_by for invalid inputs.
+ """
+ try:
+ page_size = int(self.request.GET.get("page_size", self.paginate_by))
+ if page_size <= 0:
+ return self.paginate_by
+ return min(page_size, self.max_page_size)
+ except (ValueError, TypeError):
+ return self.paginate_by
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
- request_query = self.request.GET
- context["package_search_form"] = PackageSearchForm(request_query)
- context["search"] = request_query.get("search")
- context["page_size"] = self.get_paginate_by(self.get_queryset())
+ context.update(
+ {
+ "pagination_form": PaginationForm(initial={"page_size": self.get_paginate_by()}),
+ }
+ )
return context
+
+class PackageSearch(BaseSearchView):
+ model = models.Package
+ template_name = "packages.html"
+ form_class = PackageSearchForm
+ ordering = ["type", "namespace", "name", "version"]
+
def get_queryset(self, query=None):
"""
- Return a Package queryset for the ``query``.
- Make a best effort approach to find matching packages either based
- on exact purl, partial purl or just name and namespace.
+ Return a Package queryset based on search parameters.
+
+ Args:
+ query (str, optional): Direct search query, mainly used for testing.
+ If not provided, uses form data from request.
+ Returns:
+ QuerySet: Filtered and ordered package queryset
"""
- query = query or self.request.GET.get("search") or ""
+ if query is not None:
+ # Handle direct query (used in tests)
+ return (
+ self.model.objects.search(query)
+ .with_vulnerability_counts()
+ .prefetch_related()
+ .order_by("package_url")
+ )
+
+ # Handle form submission
+ self.form = self.form_class(self.request.GET)
+ if not self.form.is_valid():
+ return self.model.objects.none()
+
+ search_query = self.form.cleaned_data.get("search", "")
return (
- self.model.objects.search(query)
+ self.model.objects.search(search_query)
.with_vulnerability_counts()
.prefetch_related()
.order_by("package_url")
)
+ def get_context_data(self, **kwargs):
+ """
+ Get the context data for template rendering.
+ Adds form and search parameters to context.
+ """
+ context = super().get_context_data(**kwargs)
+ context.update(
+ {
+ "package_search_form": self.form,
+ "search": self.request.GET.get("search"),
+ }
+ )
+ return context
+
-class VulnerabilitySearch(ListView):
+class VulnerabilitySearch(BaseSearchView):
model = models.Vulnerability
template_name = "vulnerabilities.html"
+ form_class = VulnerabilitySearchForm
ordering = ["vulnerability_id"]
- paginate_by = PAGE_SIZE
- def get_paginate_by(self, queryset):
- page_size = self.request.GET.get("page_size", "")
- return int(page_size) if page_size.isdigit() else self.paginate_by
+ def get_queryset(self, query=None):
+ """
+ Return a Vulnerability queryset based on search parameters.
+
+ Args:
+ query (str, optional): Direct search query, mainly used for testing.
+ If not provided, uses form data from request.
+ Returns:
+ QuerySet: Filtered vulnerability queryset
+ """
+ if query is not None:
+ return self.model.objects.search(query=query).with_package_counts()
+
+ self.form = self.form_class(self.request.GET)
+ if not self.form.is_valid():
+ return self.model.objects.none()
+
+ search_query = self.form.cleaned_data.get("search", "")
+ return self.model.objects.search(query=search_query).with_package_counts()
def get_context_data(self, **kwargs):
+ """
+ Get the context data for template rendering.
+ Adds form and search parameters to context.
+ """
context = super().get_context_data(**kwargs)
- request_query = self.request.GET
- context["vulnerability_search_form"] = VulnerabilitySearchForm(request_query)
- context["search"] = request_query.get("search")
- context["page_size"] = self.get_paginate_by(self.get_queryset())
+ context.update(
+ {
+ "vulnerability_search_form": self.form,
+ "search": self.request.GET.get("search"),
+ }
+ )
return context
- def get_queryset(self):
- query = self.request.GET.get("search") or ""
- return self.model.objects.search(query=query).with_package_counts()
-
- def paginate_queryset(self, queryset, page_size):
- paginator = Paginator(queryset, page_size)
- page = self.request.GET.get("page", "1")
- try:
- page_number = int(page)
- page_obj = paginator.page(page_number)
- except (ValueError, PageNotAnInteger):
- page_obj = paginator.page(1)
- except EmptyPage:
- page_obj = paginator.page(paginator.num_pages)
- return (paginator, page_obj, page_obj.object_list, page_obj.has_other_pages())
-
class PackageDetails(DetailView):
model = models.Package
@@ -275,11 +333,15 @@ def get(self, request):
Token {auth_token}
-If you did NOT request this API key, you can either ignore this email or contact us at support@nexb.com and let us know in the forward that you did not request an API key.
+If you did NOT request this API key, you can either ignore
+this email or contact us at support@nexb.com and let us know in the forward
+that you did not request an API key.
The API root is at https://public.vulnerablecode.io/api
-To learn more about using the VulnerableCode.io API, please refer to the live API documentation at https://public.vulnerablecode.io/api/docs
-To learn about VulnerableCode, refer to the general documentation at https://vulnerablecode.readthedocs.io
+To learn more about using the VulnerableCode.io API,
+please refer to the live API documentation at https://public.vulnerablecode.io/api/docs
+To learn about VulnerableCode, refer to the
+general documentation at https://vulnerablecode.readthedocs.io
--
Sincerely,
@@ -321,4 +383,4 @@ def form_valid(self, form):
return response
def get_success_url(self):
- return reverse_lazy("api_user_request")
+ return reverse_lazy("api_user_request")
\ No newline at end of file
diff --git a/vulnerablecode/static/js/pagination.js b/vulnerablecode/static/js/pagination.js
new file mode 100644
index 000000000..c09bd10f0
--- /dev/null
+++ b/vulnerablecode/static/js/pagination.js
@@ -0,0 +1,17 @@
+// static/js/pagination.js
+// This function would handles the pagination dropdown change event, maintaining existing search parameters.
+// This would also update the page size in the URL and reloads the page with the new page size parameter.
+function handlePageSizeChange(value) {
+ const url = new URL(window.location.href);
+ const params = new URLSearchParams(url.search);
+ params.set('page_size', value);
+ params.delete('page');
+ const search = params.get('search');
+ if (search) {
+ params.set('search', search);
+ }
+ const newUrl = `${window.location.pathname}?${params.toString()}`;
+ if (window.location.href !== newUrl) {
+ window.location.href = newUrl;
+ }
+}
\ No newline at end of file