From 6ca25eee367eb05191ca2a642d858f0f8d4ebc2c Mon Sep 17 00:00:00 2001 From: Rishi Garg Date: Sat, 2 Nov 2024 11:15:11 +0530 Subject: [PATCH 1/4] Minor Changes to Get Workflow tests corrected Signed-off-by: Rishi Garg --- vulnerabilities/forms.py | 67 +++++++++- .../templates/includes/pagination.html | 91 ++++++++------ vulnerabilities/templates/packages.html | 22 ++-- .../templates/vulnerabilities.html | 31 +++-- vulnerabilities/views.py | 115 +++++++++++++----- vulnerablecode/static/js/pagination.js | 17 +++ 6 files changed, 251 insertions(+), 92 deletions(-) create mode 100644 vulnerablecode/static/js/pagination.js diff --git a/vulnerabilities/forms.py b/vulnerabilities/forms.py index a00885637..1123187b6 100644 --- a/vulnerabilities/forms.py +++ b/vulnerabilities/forms.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. # @@ -12,9 +12,57 @@ from vulnerabilities.models import ApiUser +from .models import * -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", "") + + def get_queryset(self, query=None): + """ + Get queryset with search/filter/ordering applied. + Args: + query (str, optional): Direct query for testing + """ + if query is not None: + return self._search(query) + + if not self.is_valid(): + return self.model.objects.none() + + return self._search(self.clean_search()) + + +class PackageSearchForm(BaseSearchForm): + model = Package search = forms.CharField( required=True, widget=forms.TextInput( @@ -22,9 +70,18 @@ class PackageSearchForm(forms.Form): ), ) + def _search(self, query): + """Execute package-specific search logic.""" + return ( + self.model.objects.search(query) + .with_vulnerability_counts() + .prefetch_related() + .order_by("package_url") + ) -class VulnerabilitySearchForm(forms.Form): +class VulnerabilitySearchForm(BaseSearchForm): + model = Vulnerability search = forms.CharField( required=True, widget=forms.TextInput( @@ -32,6 +89,10 @@ class VulnerabilitySearchForm(forms.Form): ), ) + def _search(self, query): + """Execute vulnerability-specific search logic.""" + return self.model.objects.search(query=query).with_package_counts() + class ApiUserCreationForm(forms.ModelForm): """ 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..0739b38b6 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 %} @@ -58,8 +64,8 @@ {{ package.purl }} + href="{{ package.get_absolute_url }}?search={{ search }}" + target="_self">{{ package.purl }} {{ package.vulnerability_count }} {{ package.patched_vulnerability_count }} @@ -67,7 +73,7 @@ {% empty %} - No Package found. + No Package found. {% endfor %} @@ -75,10 +81,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 %} + +{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/templates/vulnerabilities.html b/vulnerabilities/templates/vulnerabilities.html index 023d3f97f..850e34322 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,9 +46,9 @@ {% for vulnerability in page_obj %} - {{ vulnerability.vulnerability_id }} + {{ vulnerability.vulnerability_id }} @@ -63,7 +69,7 @@ {% empty %} - No vulnerability found. + No vulnerability found. {% endfor %} @@ -71,11 +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 %} + +{% endblock %} \ No newline at end of file diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index 51cdcd049..32b748d6c 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -29,14 +29,15 @@ 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 from vulnerabilities.severity_systems import SCORING_SYSTEMS from vulnerabilities.utils import get_severity_range from vulnerablecode.settings import env PAGE_SIZE = 20 +MAX_PAGE_SIZE = 100 def purl_sort_key(purl: models.Package): @@ -62,51 +63,99 @@ 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): + """ + Get and validate the requested page size. + Required 2 positional_argument get_paginate_by(positional_argument1, positional_argument2) + """ + 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): + """Add pagination form to the template context.""" 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 + +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. - """ - query = query or self.request.GET.get("search") or "" - return ( - self.model.objects.search(query) - .with_vulnerability_counts() - .prefetch_related() - .order_by("package_url") + """Get queryset from form's search method.""" + if query is not None: + form = self.form_class() + return form.get_queryset(query=query) + + if hasattr(self, "request"): + self.form = self.form_class(self.request.GET) + return self.form.get_queryset() + + return self.model.objects.none() + + def get_context_data(self, **kwargs): + """Extends the template context with search form and search query for Packages.""" + context = super().get_context_data(**kwargs) + if not hasattr(self, "form"): + self.form = self.form_class() + context.update( + { + "package_search_form": self.form, + "search": getattr(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_queryset(self, query=None): + """Get queryset from form's search method.""" + if query is not None: + form = self.form_class() + return form.get_queryset(query=query) + + if hasattr(self, "request"): + self.form = self.form_class(self.request.GET) + return self.form.get_queryset() + + return self.model.objects.none() def get_context_data(self, **kwargs): + """Extends the template context with search form and search query for Vulnerability.""" context = super().get_context_data(**kwargs) - request_query = self.request.GET - context["vulnerability_search_form"] = VulnerabilitySearchForm(request_query) - context["search"] = request_query.get("search") + if not hasattr(self, "form"): + self.form = self.form_class() + context.update( + { + "vulnerability_search_form": self.form, + "search": getattr(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 @@ -250,11 +299,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, 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 From 1302bedda0c568304425aee7b9d09d2ce98b18f3 Mon Sep 17 00:00:00 2001 From: Rishi Garg Date: Sun, 8 Dec 2024 12:04:32 +0530 Subject: [PATCH 2/4] Pagination via Mixin - Resolving Reusability issue Signed-off-by: Rishi Garg --- vulnerabilities/forms.py | 67 +--------- vulnerabilities/pagination_mixin.py | 58 +++++++++ .../templates/includes/pagination.html | 117 +++++++++++------- vulnerabilities/templates/packages.html | 22 ++-- .../templates/vulnerabilities.html | 31 ++--- vulnerabilities/views.py | 114 +++++------------ vulnerablecode/static/js/pagination.js | 17 --- 7 files changed, 181 insertions(+), 245 deletions(-) create mode 100644 vulnerabilities/pagination_mixin.py delete mode 100644 vulnerablecode/static/js/pagination.js diff --git a/vulnerabilities/forms.py b/vulnerabilities/forms.py index 1123187b6..a00885637 100644 --- a/vulnerabilities/forms.py +++ b/vulnerabilities/forms.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/nexB/vulnerablecode for support or download. +# See https://github.com/aboutcode-org/vulnerablecode for support or download. # See https://aboutcode.org for more information about nexB OSS projects. # @@ -12,57 +12,9 @@ from vulnerabilities.models import ApiUser -from .models import * +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", "") - - def get_queryset(self, query=None): - """ - Get queryset with search/filter/ordering applied. - Args: - query (str, optional): Direct query for testing - """ - if query is not None: - return self._search(query) - - if not self.is_valid(): - return self.model.objects.none() - - return self._search(self.clean_search()) - - -class PackageSearchForm(BaseSearchForm): - model = Package search = forms.CharField( required=True, widget=forms.TextInput( @@ -70,18 +22,9 @@ class PackageSearchForm(BaseSearchForm): ), ) - def _search(self, query): - """Execute package-specific search logic.""" - return ( - self.model.objects.search(query) - .with_vulnerability_counts() - .prefetch_related() - .order_by("package_url") - ) +class VulnerabilitySearchForm(forms.Form): -class VulnerabilitySearchForm(BaseSearchForm): - model = Vulnerability search = forms.CharField( required=True, widget=forms.TextInput( @@ -89,10 +32,6 @@ class VulnerabilitySearchForm(BaseSearchForm): ), ) - def _search(self, query): - """Execute vulnerability-specific search logic.""" - return self.model.objects.search(query=query).with_package_counts() - class ApiUserCreationForm(forms.ModelForm): """ diff --git a/vulnerabilities/pagination_mixin.py b/vulnerabilities/pagination_mixin.py new file mode 100644 index 000000000..215b34872 --- /dev/null +++ b/vulnerabilities/pagination_mixin.py @@ -0,0 +1,58 @@ +class PaginatedListViewMixin: + paginate_by = 20 + max_page_size = 100 + + PAGE_SIZE_CHOICES = [ + {"value": 20, "label": "20 per page"}, + {"value": 50, "label": "50 per page"}, + {"value": 100, "label": "100 per page"}, + ] + + def get_paginate_by(self, queryset=None): + 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) + + current_page_size = self.get_paginate_by() + total_count = context["paginator"].count + + context.update( + { + "current_page_size": current_page_size, + "page_size_choices": self.PAGE_SIZE_CHOICES, + "total_count": total_count, + "page_range": self._get_page_range( + context["paginator"], context["page_obj"].number + ), + "search": self.request.GET.get("search", ""), + } + ) + + return context + + def _get_page_range(self, paginator, current_page, window=2): + if paginator.num_pages <= 5: + return range(1, paginator.num_pages + 1) + + pages = [1] + if current_page > 3: + pages.append("...") + + start_page = max(2, current_page - window) + end_page = min(paginator.num_pages - 1, current_page + window) + pages.extend(range(start_page, end_page + 1)) + + if current_page < paginator.num_pages - 2: + pages.append("...") + + if paginator.num_pages not in pages: + pages.append(paginator.num_pages) + + return pages diff --git a/vulnerabilities/templates/includes/pagination.html b/vulnerabilities/templates/includes/pagination.html index b57e83850..e794cdfd2 100644 --- a/vulnerabilities/templates/includes/pagination.html +++ b/vulnerabilities/templates/includes/pagination.html @@ -1,56 +1,77 @@ -{% if is_paginated %} - -{% endif %} \ No newline at end of file + + + diff --git a/vulnerabilities/templates/packages.html b/vulnerabilities/templates/packages.html index 0739b38b6..1f7687429 100644 --- a/vulnerabilities/templates/packages.html +++ b/vulnerabilities/templates/packages.html @@ -1,5 +1,4 @@ {% extends "base.html" %} -{% load static %} {% load humanize %} {% load widget_tweaks %} @@ -19,11 +18,6 @@
{{ page_obj.paginator.count|intcomma }} results
-
-
- {{ pagination_form.page_size }} -
-
{% if is_paginated %} {% include 'includes/pagination.html' with page_obj=page_obj %} {% endif %} @@ -64,8 +58,8 @@ {{ package.purl }} + href="{{ package.get_absolute_url }}?search={{ search }}" + target="_self">{{ package.purl }} {{ package.vulnerability_count }} {{ package.patched_vulnerability_count }} @@ -73,7 +67,7 @@ {% empty %} - No Package found. + No Package found. {% endfor %} @@ -81,10 +75,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 +{% endblock %} diff --git a/vulnerabilities/templates/vulnerabilities.html b/vulnerabilities/templates/vulnerabilities.html index 850e34322..023d3f97f 100644 --- a/vulnerabilities/templates/vulnerabilities.html +++ b/vulnerabilities/templates/vulnerabilities.html @@ -1,5 +1,4 @@ {% extends "base.html" %} -{% load static %} {% load humanize %} {% load widget_tweaks %} @@ -19,14 +18,9 @@
{{ page_obj.paginator.count|intcomma }} results
-
-
- {{ pagination_form.page_size }} -
-
- {% 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 %} @@ -46,9 +40,9 @@ {% for vulnerability in page_obj %} - {{ vulnerability.vulnerability_id }} + {{ vulnerability.vulnerability_id }} @@ -69,7 +63,7 @@ {% empty %} - No vulnerability found. + No vulnerability found. {% endfor %} @@ -77,10 +71,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 %} \ No newline at end of file + +{% endblock %} diff --git a/vulnerabilities/views.py b/vulnerabilities/views.py index 6af4b7aff..6a38fdc63 100644 --- a/vulnerabilities/views.py +++ b/vulnerabilities/views.py @@ -29,15 +29,16 @@ 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 from vulnerabilities.severity_systems import SCORING_SYSTEMS from vulnerabilities.utils import get_severity_range from vulnerablecode.settings import env +from .pagination_mixin import PaginatedListViewMixin + PAGE_SIZE = 20 -MAX_PAGE_SIZE = 100 def purl_sort_key(purl: models.Package): @@ -63,99 +64,48 @@ def get_purl_version_class(purl: models.Package): return purl_version_class -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): - """ - Get and validate the requested page size. - Required 2 positional_argument get_paginate_by(positional_argument1, positional_argument2) - """ - 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): - """Add pagination form to the template context.""" - context = super().get_context_data(**kwargs) - context.update( - { - "pagination_form": PaginationForm(initial={"page_size": self.get_paginate_by()}), - } - ) - return context - +class PackageSearch(PaginatedListViewMixin, ListView): + """ + View for searching and displaying packages with pagination. + """ -class PackageSearch(BaseSearchView): model = models.Package template_name = "packages.html" - form_class = PackageSearchForm ordering = ["type", "namespace", "name", "version"] - def get_queryset(self, query=None): - """Get queryset from form's search method.""" - if query is not None: - form = self.form_class() - return form.get_queryset(query=query) - - if hasattr(self, "request"): - self.form = self.form_class(self.request.GET) - return self.form.get_queryset() - - return self.model.objects.none() - def get_context_data(self, **kwargs): - """Extends the template context with search form and search query for Packages.""" context = super().get_context_data(**kwargs) - if not hasattr(self, "form"): - self.form = self.form_class() - context.update( - { - "package_search_form": self.form, - "search": getattr(self.request, "GET", {}).get("search"), - } - ) + context["package_search_form"] = PackageSearchForm(self.request.GET) return context + def get_queryset(self, query=None): + query = query or self.request.GET.get("search") or "" + return ( + self.model.objects.search(query) + .with_vulnerability_counts() + .prefetch_related() + .order_by("package_url") + ) + + +class VulnerabilitySearch(PaginatedListViewMixin, ListView): + """ + View for searching and displaying vulnerabilities with pagination. + """ -class VulnerabilitySearch(BaseSearchView): model = models.Vulnerability template_name = "vulnerabilities.html" - form_class = VulnerabilitySearchForm ordering = ["vulnerability_id"] - def get_queryset(self, query=None): - """Get queryset from form's search method.""" - if query is not None: - form = self.form_class() - return form.get_queryset(query=query) - - if hasattr(self, "request"): - self.form = self.form_class(self.request.GET) - return self.form.get_queryset() - - return self.model.objects.none() - def get_context_data(self, **kwargs): - """Extends the template context with search form and search query for Vulnerability.""" context = super().get_context_data(**kwargs) - if not hasattr(self, "form"): - self.form = self.form_class() - context.update( - { - "vulnerability_search_form": self.form, - "search": getattr(self.request, "GET", {}).get("search"), - } - ) + context["vulnerability_search_form"] = VulnerabilitySearchForm(self.request.GET) 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 @@ -317,15 +267,11 @@ 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, diff --git a/vulnerablecode/static/js/pagination.js b/vulnerablecode/static/js/pagination.js deleted file mode 100644 index c09bd10f0..000000000 --- a/vulnerablecode/static/js/pagination.js +++ /dev/null @@ -1,17 +0,0 @@ -// 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 From 3b2194625e0a05d860a02bb099b5f440faec7a20 Mon Sep 17 00:00:00 2001 From: Rishi Garg Date: Tue, 10 Dec 2024 19:35:42 +0530 Subject: [PATCH 3/4] Resuable Pagination Code - SCIO+VCIO Signed-off-by: Rishi Garg --- vulnerabilities/pagination_mixin.py | 87 ++++++++++++------- .../templates/includes/pagination.html | 46 ++++++++++ vulnerabilities/views.py | 6 ++ 3 files changed, 110 insertions(+), 29 deletions(-) diff --git a/vulnerabilities/pagination_mixin.py b/vulnerabilities/pagination_mixin.py index 215b34872..9d6c974f1 100644 --- a/vulnerabilities/pagination_mixin.py +++ b/vulnerabilities/pagination_mixin.py @@ -1,4 +1,8 @@ class PaginatedListViewMixin: + """ + A mixin that adds pagination functionality to ListView-based views. + """ + paginate_by = 20 max_page_size = 100 @@ -9,6 +13,9 @@ class PaginatedListViewMixin: ] def get_paginate_by(self, queryset=None): + """ + Get the number of items to paginate by from the request. + """ try: page_size = int(self.request.GET.get("page_size", self.paginate_by)) if page_size <= 0: @@ -17,42 +24,64 @@ def get_paginate_by(self, queryset=None): except (ValueError, TypeError): return self.paginate_by - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) + def get_pagination_context(self, paginator, page_obj): + """ + Generate pagination-related context data, preserving filters. + """ + if not paginator or not page_obj: + return {} current_page_size = self.get_paginate_by() - total_count = context["paginator"].count - - context.update( - { - "current_page_size": current_page_size, - "page_size_choices": self.PAGE_SIZE_CHOICES, - "total_count": total_count, - "page_range": self._get_page_range( - context["paginator"], context["page_obj"].number - ), - "search": self.request.GET.get("search", ""), - } - ) + total_count = paginator.count - return context + query_params = self.request.GET.copy() + query_params.pop("page", None) + + base_query_string = query_params.urlencode() + base_url = f"?{base_query_string}" if base_query_string else "?" - def _get_page_range(self, paginator, current_page, window=2): + pages = [] if paginator.num_pages <= 5: - return range(1, paginator.num_pages + 1) + pages = [str(i) for i in range(1, paginator.num_pages + 1)] + else: + pages.append("1") - pages = [1] - if current_page > 3: - pages.append("...") + if page_obj.number > 3: + pages.append("...") - start_page = max(2, current_page - window) - end_page = min(paginator.num_pages - 1, current_page + window) - pages.extend(range(start_page, end_page + 1)) + start_page = max(2, page_obj.number - 2) + end_page = min(paginator.num_pages - 1, page_obj.number + 2) + pages.extend(str(i) for i in range(start_page, end_page + 1)) - if current_page < paginator.num_pages - 2: - pages.append("...") + if page_obj.number < paginator.num_pages - 2: + pages.append("...") - if paginator.num_pages not in pages: - pages.append(paginator.num_pages) + if str(paginator.num_pages) not in pages: + pages.append(str(paginator.num_pages)) - return pages + return { + "current_page_size": current_page_size, + "page_size_choices": self.PAGE_SIZE_CHOICES, + "total_count": total_count, + "page_range": pages, + "search": self.request.GET.get("search", ""), + "base_url": base_url, + "previous_page_url": f"{base_url}&page={page_obj.previous_page_number}" + if page_obj.has_previous() + else None, + "next_page_url": f"{base_url}&page={page_obj.next_page_number}" + if page_obj.has_next() + else None, + } + + def get_context_data(self, **kwargs): + """ + Add pagination context to the existing context data. + """ + context = super().get_context_data(**kwargs) + paginator = context.get("paginator") + page_obj = context.get("page_obj") + pagination_context = self.get_pagination_context(paginator, page_obj) + context.update(pagination_context) + + return context diff --git a/vulnerabilities/templates/includes/pagination.html b/vulnerabilities/templates/includes/pagination.html index e794cdfd2..451cc8b23 100644 --- a/vulnerabilities/templates/includes/pagination.html +++ b/vulnerabilities/templates/includes/pagination.html @@ -12,30 +12,52 @@ +<<<<<<< Updated upstream {% if page_obj.paginator.num_pages > 1 %}