11import logging
22import re
33
4- from django .core .paginator import Paginator
4+ from django .core .paginator import EmptyPage , PageNotAnInteger , Paginator
55from django .db .models .query import QuerySet
66from 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
0 commit comments