Skip to content

Commit c75a134

Browse files
committed
Pagination Mixin optimisation and JS code removed
Signed-off-by: RISHI GARG <134256793+Rishi-source@users.noreply.github.com>
1 parent 936c5bb commit c75a134

File tree

2 files changed

+122
-133
lines changed

2 files changed

+122
-133
lines changed

vulnerabilities/pagination.py

Lines changed: 62 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22
import re
33

4-
from django.core.paginator import Paginator
4+
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
55
from django.db.models.query import QuerySet
66
from rest_framework.pagination import PageNumberPagination
77

@@ -18,46 +18,56 @@ class PaginatedListViewMixin:
1818
A mixin that adds pagination functionality to ListView-based views.
1919
"""
2020

21-
paginate_by = 20
21+
paginate_default = 20
2222
max_page_size = 100
23-
PAGE_SIZE_CHOICES = [
23+
page_size_choices = [
2424
{"value": 20, "label": "20 per page"},
2525
{"value": 50, "label": "50 per page"},
2626
{"value": 100, "label": "100 per page"},
2727
]
28+
29+
max_pages_without_truncation = 5 # it is a value for number of pages without truncation like is total number of pages are less than this number the pagination will show all pages.
30+
pages_around_current = 2 # number of pages to be shown around current page
31+
truncation_threshold_start = 4 # it is a threshold for start of truncation
32+
truncation_threshold_end = 3 # it is a threshold for end of truncation
2833

2934
def get_queryset(self):
3035
"""
3136
Ensure a queryset is always available
3237
"""
3338
try:
3439
queryset = super().get_queryset()
35-
if not queryset:
36-
queryset = self.model.objects.all()
37-
if not isinstance(queryset, QuerySet):
38-
queryset = self.model.objects.all()
39-
return queryset
4040
except Exception as e:
4141
logger.error(f"Error in get_queryset: {e}")
42-
return self.model.objects.all()
42+
return self.model.objects.none()
43+
44+
if not queryset or not isinstance(queryset, QuerySet):
45+
queryset = self.model.objects.none()
46+
return queryset
4347

4448
def sanitize_page_size(self, raw_page_size):
4549
"""
4650
Sanitize page size input to prevent XSS and injection attempts.
4751
"""
4852
if not raw_page_size:
49-
return self.paginate_by
50-
clean_page_size = re.sub(r"\D", "", str(raw_page_size))
53+
return self.paginate_default
54+
55+
clean_page_size = re.sub(r"\D", "", str(raw_page_size)) # it remove all non-digit characters like if 50abcd is their then it takes out 50
56+
if not clean_page_size:
57+
return self.paginate_default
58+
5159
try:
52-
page_size = int(clean_page_size) if clean_page_size else self.paginate_by
53-
valid_sizes = {choice["value"] for choice in self.PAGE_SIZE_CHOICES}
54-
if page_size not in valid_sizes:
55-
logger.warning(f"Attempted to use unauthorized page size: {page_size}")
56-
return self.paginate_by
57-
return page_size
60+
page_size = int(clean_page_size)
5861
except (ValueError, TypeError):
59-
logger.info("Empty or invalid page_size input attempted")
60-
return self.paginate_by
62+
logger.info("Invalid page_size input attempted")
63+
return self.paginate_default
64+
65+
valid_sizes = {choice["value"] for choice in self.page_size_choices}
66+
if page_size not in valid_sizes:
67+
logger.warning(f"Attempted to use unauthorized page size: {page_size}")
68+
return self.paginate_default
69+
70+
return page_size
6171

6272
def get_paginate_by(self, queryset=None):
6373
"""
@@ -72,57 +82,47 @@ def get_page_range(self, paginator, page_obj):
7282
"""
7383
num_pages = paginator.num_pages
7484
current_page = page_obj.number
75-
if num_pages <= 7:
76-
return list(range(1, num_pages + 1))
77-
pages = []
78-
pages.append(1)
79-
if current_page > 4:
85+
if num_pages <= self.max_pages_without_truncation:
86+
return list(map(str, range(1, num_pages + 1)))
87+
pages = [1]
88+
89+
if current_page > self.truncation_threshold_start:
8090
pages.append("...")
81-
start = max(2, current_page - 2)
82-
end = min(num_pages - 1, current_page + 2)
91+
start = max(2, current_page - self.pages_around_current)
92+
end = min(num_pages - 1, current_page + self.pages_around_current)
8393
pages.extend(range(start, end + 1))
84-
if current_page < num_pages - 3:
94+
if current_page < num_pages - self.truncation_threshold_end:
8595
pages.append("...")
8696
if num_pages > 1:
8797
pages.append(num_pages)
88-
return [str(p) for p in pages]
98+
return list(map(str, pages))
8999

90100
def paginate_queryset(self, queryset, page_size):
91-
"""
92-
Robust pagination with comprehensive error handling
93-
"""
101+
if not queryset:
102+
queryset = self.model.objects.none()
103+
paginator = Paginator(queryset, page_size)
104+
try:
105+
page_number = int(self.request.GET.get("page", "1"))
106+
except (ValueError, TypeError):
107+
logger.error("Invalid page number input")
108+
page_number = 1
109+
page_number = max(1, min(page_number, paginator.num_pages))
94110
try:
95-
if not queryset or queryset.count() == 0:
96-
queryset = self.model.objects.all()
97-
paginator = Paginator(queryset, page_size)
98-
page_params = self.request.GET.getlist("page")
99-
page_number = page_params[-1] if page_params else "1"
100-
try:
101-
page_number = int(re.sub(r"\D", "", str(page_number)))
102-
if not page_number:
103-
page_number = 1
104-
except (ValueError, TypeError):
105-
page_number = 1
106-
page_number = max(1, min(page_number, paginator.num_pages))
107111
page = paginator.page(page_number)
108-
return (paginator, page, page.object_list, page.has_other_pages())
109-
except Exception as e:
110-
logger.error(f"Pagination error: {e}")
111-
queryset = self.model.objects.all()
112-
paginator = Paginator(queryset, page_size)
112+
except (EmptyPage, PageNotAnInteger) as e:
113+
logger.error(f"Specific pagination error: {e}")
113114
page = paginator.page(1)
114-
return (paginator, page, page.object_list, page.has_other_pages())
115+
return (paginator, page, page.object_list, page.has_other_pages())
115116

116117
def get_context_data(self, **kwargs):
117118
"""
118119
Return a mapping of pagination-related context data, preserving filters.
119120
"""
120-
queryset = self.get_queryset()
121+
queryset = kwargs.pop('queryset', None) or self.get_queryset()
121122
page_size = self.get_paginate_by()
122123
paginator, page, object_list, is_paginated = self.paginate_queryset(queryset, page_size)
123124
page_range = self.get_page_range(paginator, page)
124125

125-
search = self.request.GET.get("search", "")
126126

127127
context = super().get_context_data(
128128
object_list=object_list,
@@ -132,15 +132,16 @@ def get_context_data(self, **kwargs):
132132
**kwargs,
133133
)
134134

135-
context.update(
136-
{
137-
"current_page_size": page_size,
138-
"page_size_choices": self.PAGE_SIZE_CHOICES,
139-
"total_count": paginator.count,
140-
"page_range": page_range,
141-
"search": search,
142-
"previous_page_url": page.previous_page_number() if page.has_previous() else None,
143-
"next_page_url": page.next_page_number() if page.has_next() else None,
144-
}
135+
previous_page_url = page.previous_page_number() if page.has_previous() else None
136+
next_page_url = page.next_page_number() if page.has_next() else None
137+
context.update({
138+
"current_page_size": page_size,
139+
"page_size_choices": self.page_size_choices,
140+
"total_count": paginator.count,
141+
"page_range": page_range,
142+
"search": self.request.GET.get("search", ""),
143+
"previous_page_url": previous_page_url,
144+
"next_page_url": next_page_url,
145+
}
145146
)
146147
return context
Lines changed: 60 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,64 @@
11
<div class="pagination-controls mb-4">
2-
<div class="is-flex is-justify-content-center mb-3">
3-
<div class="select is-small {% if total_count < current_page_size %}is-disabled{% endif %}">
4-
<select onchange="handlePageSizeChange(this.value)"
5-
{% if total_count < current_page_size %}disabled="disabled"{% endif %}>
6-
{% for choice in page_size_choices %}
7-
<option value="{{ choice.value }}"
8-
{% if choice.value == current_page_size %}selected{% endif %}>
9-
{{ choice.label }}
10-
</option>
11-
{% endfor %}
12-
</select>
13-
</div>
2+
<div class="is-flex is-justify-content-center mb-3">
3+
<form method="get" action="">
4+
{% if search %}
5+
<input type="hidden" name="search" value="{{ search }}">
6+
{% endif %}
7+
<div class="select is-small {% if total_count < current_page_size %}is-disabled{% endif %}">
8+
<select name="page_size" onchange="this.form.submit()"
9+
{% if total_count < current_page_size %}disabled="disabled"{% endif %}>
10+
{% for choice in page_size_choices %}
11+
<option value="{{ choice.value }}"
12+
{% if choice.value == current_page_size %}selected{% endif %}>
13+
{{ choice.label }}
14+
</option>
15+
{% endfor %}
16+
</select>
17+
</div>
18+
</form>
19+
</div>
20+
21+
{% if page_obj.paginator.num_pages > 1 %}
22+
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
23+
{% if page_obj.has_previous %}
24+
<a href="?page={{ page_obj.previous_page_number }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
25+
class="pagination-previous">Previous</a>
26+
{% else %}
27+
<button class="pagination-previous" disabled>Previous</button>
28+
{% endif %}
29+
30+
{% if page_obj.has_next %}
31+
<a href="?page={{ page_obj.next_page_number }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
32+
class="pagination-next">Next</a>
33+
{% else %}
34+
<button class="pagination-next" disabled>Next</button>
35+
{% endif %}
36+
<ul class="pagination-list">
37+
{% for page_num in page_range %}
38+
{% if page_num == '...' %}
39+
<li><span class="pagination-ellipsis">&hellip;</span></li>
40+
{% else %}
41+
<li>
42+
<a href="?page={{ page_num }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
43+
class="pagination-link {% if page_num == page_obj.number %}is-current{% endif %}"
44+
aria-label="Go to page {{ page_num }}"
45+
{% if page_num == page_obj.number %}aria-current="page"{% endif %}>
46+
{{ page_num }}
47+
</a>
48+
</li>
49+
{% endif %}
50+
{% endfor %}
51+
</ul>
52+
</nav>
53+
{% endif %}
1454
</div>
15-
16-
{% if page_obj.paginator.num_pages > 1 %}
17-
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
18-
{% if page_obj.has_previous %}
19-
<a href="?page={{ page_obj.previous_page_number }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
20-
class="pagination-previous">Previous</a>
21-
{% else %}
22-
<button class="pagination-previous" disabled>Previous</button>
23-
{% endif %}
24-
25-
{% if page_obj.has_next %}
26-
<a href="?page={{ page_obj.next_page_number }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
27-
class="pagination-next">Next</a>
28-
{% else %}
29-
<button class="pagination-next" disabled>Next</button>
30-
{% endif %}
31-
<ul class="pagination-list">
32-
{% for page_num in page_range %}
33-
{% if page_num == '...' %}
34-
<li><span class="pagination-ellipsis">&hellip;</span></li>
35-
{% else %}
36-
<li>
37-
<a href="?page={{ page_num }}&search={{ search|urlencode }}&page_size={{ current_page_size }}"
38-
class="pagination-link {% if page_num == page_obj.number %}is-current{% endif %}"
39-
aria-label="Go to page {{ page_num }}"
40-
{% if page_num == page_obj.number %}aria-current="page"{% endif %}>
41-
{{ page_num }}
42-
</a>
43-
</li>
44-
{% endif %}
45-
{% endfor %}
46-
</ul>
47-
</nav>
48-
{% endif %}
49-
</div>
50-
51-
<style>
52-
.select.is-disabled {
53-
opacity: 0.7;
54-
cursor: not-allowed;
55-
}
56-
.select.is-disabled select {
57-
cursor: not-allowed;
58-
}
59-
</style>
60-
61-
<script>
62-
function handlePageSizeChange(value) {
63-
const url = new URL(window.location.href);
64-
const params = new URLSearchParams(url.search);
65-
params.set('page_size', value);
66-
params.delete('page');
6755

68-
const search = params.get('search');
69-
if (search) {
70-
params.set('search', search);
56+
<style>
57+
.select.is-disabled {
58+
opacity: 0.7;
59+
cursor: not-allowed;
7160
}
72-
73-
const newUrl = `${window.location.pathname}?${params.toString()}`;
74-
window.location.href = newUrl;
75-
}
76-
</script>
61+
.select.is-disabled select {
62+
cursor: not-allowed;
63+
}
64+
</style>

0 commit comments

Comments
 (0)