diff --git a/vulnerabilities/forms.py b/vulnerabilities/forms.py
index a00885637..8f887cdca 100644
--- a/vulnerabilities/forms.py
+++ b/vulnerabilities/forms.py
@@ -3,18 +3,49 @@
# 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.
#
from django import forms
from django.core.validators import validate_email
-
+from .models import *
from vulnerabilities.models import ApiUser
-class PackageSearchForm(forms.Form):
+class PaginationForm(forms.Form):
+ """Form to handle page size selection across the application."""
+
+ PAGE_CHOICES = [
+ ("20", "20 per page"),
+ ("50", "50 per page"),
+ ("100", "100 per page"),
+ ]
+
+ page_size = forms.ChoiceField(
+ choices=PAGE_CHOICES,
+ initial="20",
+ required=False,
+ widget=forms.Select(
+ attrs={
+ "class": "select is-small",
+ "onchange": "handlePageSizeChange(this.value)",
+ "id": "page-size-select",
+ }
+ ),
+ )
+
+class BaseSearchForm(forms.Form):
+ """Base form for implementing search functionality."""
+
+ search = forms.CharField(required=True)
+
+ def clean_search(self):
+ return self.cleaned_data.get("search", "")
+
+
+class PackageSearchForm(BaseSearchForm):
search = forms.CharField(
required=True,
widget=forms.TextInput(
@@ -23,8 +54,7 @@ class PackageSearchForm(forms.Form):
)
-class VulnerabilitySearchForm(forms.Form):
-
+class VulnerabilitySearchForm(BaseSearchForm):
search = forms.CharField(
required=True,
widget=forms.TextInput(
diff --git a/vulnerabilities/templates/includes/pagination.html b/vulnerabilities/templates/includes/pagination.html
index 0d6dad430..b57e83850 100644
--- a/vulnerabilities/templates/includes/pagination.html
+++ b/vulnerabilities/templates/includes/pagination.html
@@ -1,39 +1,56 @@
-
\ No newline at end of file
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/vulnerabilities/templates/packages.html b/vulnerabilities/templates/packages.html
index 1f7687429..9e3e23eb2 100644
--- a/vulnerabilities/templates/packages.html
+++ b/vulnerabilities/templates/packages.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+{% load static %}
{% load humanize %}
{% load widget_tweaks %}
@@ -18,6 +19,11 @@
{{ page_obj.paginator.count|intcomma }} results
+
+
+ {{ pagination_form.page_size }}
+
+
{% if is_paginated %}
{% include 'includes/pagination.html' with page_obj=page_obj %}
{% endif %}
@@ -57,9 +63,8 @@
{% for package in page_obj %}
- {{ package.purl }}
+ {{ package.purl }}
|
{{ package.vulnerability_count }} |
{{ package.patched_vulnerability_count }} |
@@ -67,7 +72,7 @@
{% empty %}
- No Package found.
+ No Package found.
|
{% endfor %}
@@ -75,10 +80,11 @@
- {% 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 %}
+
diff --git a/vulnerabilities/templates/vulnerabilities.html b/vulnerabilities/templates/vulnerabilities.html
index 023d3f97f..d7d2484aa 100644
--- a/vulnerabilities/templates/vulnerabilities.html
+++ b/vulnerabilities/templates/vulnerabilities.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+{% load static %}
{% load humanize %}
{% load widget_tweaks %}
@@ -18,9 +19,14 @@
{{ page_obj.paginator.count|intcomma }} results
- {% if is_paginated %}
- {% include 'includes/pagination.html' with page_obj=page_obj %}
- {% endif %}
+
+
+ {{ pagination_form.page_size }}
+
+
+ {% if is_paginated %}
+ {% include 'includes/pagination.html' with page_obj=page_obj %}
+ {% endif %}
@@ -40,10 +46,8 @@
{% for vulnerability in page_obj %}
- {{ vulnerability.vulnerability_id }}
-
+ {{ vulnerability.vulnerability_id }}
|
{% for alias in vulnerability.alias %}
@@ -62,8 +66,8 @@
|
{% empty %}
-
- No vulnerability found.
+ |
+ No vulnerability found.
|
{% endfor %}
@@ -71,11 +75,11 @@
-
- {% 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 %}
+
diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py
index 51cdcd049..9b1f81d06 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
@@ -25,11 +25,12 @@
from django.views.generic.list import ListView
from univers.version_range import RANGE_CLASS_BY_SCHEMES
from univers.version_range import AlpineLinuxVersionRange
-
+from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from vulnerabilities import models
from vulnerabilities.forms import ApiUserCreationForm
from vulnerabilities.forms import PackageSearchForm
from vulnerabilities.forms import VulnerabilitySearchForm
+from vulnerabilities.forms import PaginationForm
from vulnerabilities.models import VulnerabilityStatusType
from vulnerabilities.severity_systems import EPSS
from vulnerabilities.severity_systems import SCORING_SYSTEMS
@@ -37,7 +38,7 @@
from vulnerablecode.settings import env
PAGE_SIZE = 20
-
+MAX_PAGE_SIZE = 100
def purl_sort_key(purl: models.Package):
"""
@@ -62,52 +63,84 @@ 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=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.update({
+ 'pagination_form': PaginationForm(
+ initial={'page_size': self.get_paginate_by()}
+ ),
+ })
return context
- 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.
- """
- query = query or self.request.GET.get("search") or ""
+
+class PackageSearch(BaseSearchView):
+ model = models.Package
+ template_name = "packages.html"
+ ordering = ["type", "namespace", "name", "version"]
+
+ def get_queryset(self):
+ """Return search results from the form."""
+ self.form = PackageSearchForm(self.request.GET)
+ if not self.form.is_valid():
+ return self.model.objects.none()
return (
- self.model.objects.search(query)
+ self.model.objects.search(self.form.cleaned_data.get("search", ""))
.with_vulnerability_counts()
.prefetch_related()
.order_by("package_url")
)
+ def get_context_data(self, **kwargs):
+ 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"
ordering = ["vulnerability_id"]
- paginate_by = PAGE_SIZE
+
+ def get_queryset(self):
+ """Return search results from the form."""
+ self.form = VulnerabilitySearchForm(self.request.GET)
+ if not self.form.is_valid():
+ return self.model.objects.none()
+ return (
+ self.model.objects.search(self.form.cleaned_data.get("search", ""))
+ .with_package_counts()
+ )
def get_context_data(self, **kwargs):
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.update({
+ "vulnerability_search_form": self.form,
+ "search": self.request.GET.get("search"),
+ })
return context
-
- def get_queryset(self, query=None):
- query = query or self.request.GET.get("search") or ""
- return self.model.objects.search(query=query).with_package_counts()
-
-
class PackageDetails(DetailView):
model = models.Package
template_name = "package_details.html"
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