-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pagination Updated - Items per page option added #1625
base: main
Are you sure you want to change the base?
Conversation
Hi @pombredanne @TG1999 @keshav-space @Hritik14 , Can you please review this PR It has passed all workflow tests and was also working fine on my local server. Thank you |
d8068aa
to
c28765a
Compare
e4a1a18
to
8d177c8
Compare
Signed-off-by: Rishi Garg <[email protected]>
8d177c8
to
6ca25ee
Compare
@tdruez please see the changes now workflow tests are also successful . |
@tdruez I would request you to please review the PR I have made changes In the code after you requested some changes after first review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the various comments.
Generally, this implementation seems too complex and not "reusable" enough at it requires to many moving parts.
vulnerabilities/forms.py
Outdated
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unwanted changes
vulnerabilities/forms.py
Outdated
@@ -12,26 +12,87 @@ | |||
|
|||
from vulnerabilities.models import ApiUser | |||
|
|||
from .models import * |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid * imports.
vulnerabilities/forms.py
Outdated
widget=forms.Select( | ||
attrs={ | ||
"class": "select is-small", | ||
"onchange": "handlePageSizeChange(this.value)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid inline JavaScript.
vulnerabilities/forms.py
Outdated
def clean_search(self): | ||
return self.cleaned_data.get("search", "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of this?
vulnerabilities/forms.py
Outdated
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()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the need for this logic?
vulnerabilities/forms.py
Outdated
def _search(self, query): | ||
"""Execute package-specific search logic.""" | ||
return ( | ||
self.model.objects.search(query) | ||
.with_vulnerability_counts() | ||
.prefetch_related() | ||
.order_by("package_url") | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not following the Django forms conventions.
vulnerabilities/forms.py
Outdated
def _search(self, query): | ||
"""Execute vulnerability-specific search logic.""" | ||
return self.model.objects.search(query=query).with_package_counts() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as above.
<a href="?page={{ page_obj.previous_page_number }}&search={{ search|urlencode }}&page_size={{ page_obj.paginator.per_page }}" | ||
class="pagination-previous">Previous</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if other filters are active, those are lost when using the pagination?
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the complexity of those templates should likely be handled in Python.
vulnerabilities/views.py
Outdated
model = models.Package | ||
template_name = "packages.html" | ||
ordering = ["type", "namespace", "name", "version"] | ||
class BaseSearchView(ListView): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use the django_filters.views.FilterView
instead of re-implementing it?
@tdruez what do you mean by "reusable" here. I would code the implementation again to make it more simpler and satisfy the required needs. |
@Rishi-source Let's say I want to add this pagination on another Django view, could you document the few steps I need to do with this current implementation? This would help to figure out where we can improve the "reusability". |
170df8c
to
1302bed
Compare
{"value": 100, "label": "100 per page"}, | ||
] | ||
|
||
def get_paginate_by(self, queryset=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function handles page size from URL parameters. Sets default to 20 items, max of 100. Validates to ensure positive integers only. Returns default if invalid values provided.
vulnerabilities/pagination_mixin.py
Outdated
except (ValueError, TypeError): | ||
return self.paginate_by | ||
|
||
def get_context_data(self, **kwargs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It adds pagination data to template context - current page size, size choices (can be handled by altering PAGE_SIZE_CHOICES) , total count and page range. it also preserves search params across pagination.
vulnerabilities/pagination_mixin.py
Outdated
|
||
return context | ||
|
||
def _get_page_range(self, paginator, current_page, window=2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it creates page number sequence. Shows all numbers for small sets (<5 pages). For larger sets, shows first/last pages around current page with (...) for gaps.
@@ -36,6 +36,8 @@ | |||
from vulnerabilities.utils import get_severity_range | |||
from vulnerablecode.settings import env | |||
|
|||
from .pagination_mixin import PaginatedListViewMixin |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import the Mixin and the PaginatedListViewMixin class.
@@ -62,25 +64,21 @@ def get_purl_version_class(purl: models.Package): | |||
return purl_version_class | |||
|
|||
|
|||
class PackageSearch(ListView): | |||
class PackageSearch(PaginatedListViewMixin, ListView): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the PaginatedListViewMixin in it and this would convert any ListView into paginated class.
Hi @tdruez , I have altered code to improve the reusability for that I have made a file |
window.location.href = newUrl; | ||
} | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This js function manages URL parameters when changing pagination size. It update the page size parameter while preserving search terms, and remove the current page parameter to reset pagination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any risk of XSS there?
@Rishi-source Thanks but looking at multiple comments is hard to follow. Could you also provide a combined documentation of the few steps required to reuse this pagination in another app? |
Pagination Implementation DocumentationCore Components of Code
Implementation Steps
from django.views.generic import ListView
from .pagination_mixins import PaginatedListViewMixin # Make sure to import the mixin
class MyListView(PaginatedListViewMixin, ListView): # Please make sure to put "PaginatedListViewMixin" Above all the mixins in case if there are 2 or more mixins are already their as position of mixin will matter here
model = MyModel
template_name = "my_template.html"
def get_queryset(self):
return super().get_queryset().order_by('id')
{% extends "base.html" %}
{% block content %}
{# Like This #}
{% if is_paginated %}
{% include 'pagination.html' with page_obj=page_obj %}
{% endif %}
{% endblock %} Customization Options
class MyListView(PaginatedListViewMixin, ListView):
PAGE_SIZE_CHOICES = [
{"value": 10, "label": "10 per page"},
{"value": 25, "label": "25 per page"},
{"value": 50, "label": "50 per page"},
]
paginate_by = 20 # Set the Default size
def get_context_data(self, **kwargs):
# Change window variable to show more/fewer pages around current page
context = super().get_context_data(**kwargs)
context['page_range'] = self._get_page_range(
context['paginator'],
context['page_obj'].number,
window=3 # Show 3 pages on each side i.e. left and right
return context Notes
Common Issues and Solutions
Remember that this implementation assumes:
|
@tdruez Here’s the documentation on implementing pagination using this approach. Please review and let me know if any changes are required. |
Signed-off-by: Rishi Garg <[email protected]>
Signed-off-by: Rishi Garg <[email protected]>
Hi @tdruez and @pombredanne , I have implemented this pagination code in both VCIO and SCIO and it is working nice.It preserved all the filter and add a option for the user to select the items per page. I have already shared the screenshot for the VCIO pagination view here are the screenshot for SCIO pagination. @tdruez you just have to follow the steps which I have provided in the Documentation for implementing this pagination. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks
Here is some feedback!
The PR title should be using imperative style:
Not Pagination Updated - Items per page option added
But rather:
"Improve pagination in web ui"
then add details in the body.
Could you also add tests? PaginatedListViewMixin is not trivial code
Also as @tdruez mentioned in late November, the template should have little to no logic and instead the logic should be in the Python code.
Get the number of items to paginate by from the request. | ||
""" | ||
try: | ||
page_size = int(self.request.GET.get("page_size", self.paginate_by)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the standard approach to get a request arg?
|
||
def get_pagination_context(self, paginator, page_obj): | ||
""" | ||
Generate pagination-related context data, preserving filters. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generate pagination-related context data, preserving filters. | |
Return a mapping of pagination-related context data, preserving filters. |
query_params = self.request.GET.copy() | ||
query_params.pop("page", None) | ||
|
||
base_query_string = query_params.urlencode() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the right way to generate a URL query string? Is there a better way?
window.location.href = newUrl; | ||
} | ||
</script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there any risk of XSS there?
@@ -0,0 +1,87 @@ | |||
class PaginatedListViewMixin: | |||
""" | |||
A mixin that adds pagination functionality to ListView-based views. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would call the file pagination.py instead
Add pagination to VCIO & SCIO #1616 #1617 #1618
This change aligns Package Search functionality and Vulnerability Search, improving consistency across the application and enhancing user control over result display.
For Package Search:
For Vulnerability Search:
Signed-off-by: Rishi Garg [email protected]