Ticket #8408: paginator-patch.diff
File paginator-patch.diff, 13.2 KB (added by , 13 years ago) |
---|
-
./django/contrib/admin/options.py
commit 263bbe728adafa2b59671b537ce92a1ec5bee06b Author: Malcolm Box <Malcolm Box boxm@livetalkback.com> Date: Thu May 26 14:31:39 2011 +0100 Patches to Django to allow admin screens to use a Paginator that doesn't use count() because that's expensive diff --git ./django/contrib/admin/options.py ./django/contrib/admin/options.py index 43c5503..25d32eb 100644
class ModelAdmin(BaseModelAdmin): 1151 1151 else: 1152 1152 action_form = None 1153 1153 1154 selection_note_all = ungettext('%(total_count)s selected',1155 'All %(total_count)s selected', cl.result_count)1156 1157 1154 context = { 1158 1155 'module_name': force_unicode(opts.verbose_name_plural), 1159 1156 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)}, 1160 'selection_note_all': selection_note_all % {'total_count': cl.result_count},1161 1157 'title': cl.title, 1162 1158 'is_popup': cl.is_popup, 1163 1159 'cl': cl, … … class ModelAdmin(BaseModelAdmin): 1170 1166 'actions_on_bottom': self.actions_on_bottom, 1171 1167 'actions_selection_counter': self.actions_selection_counter, 1172 1168 } 1169 1170 if hasattr(cl, 'result_count'): 1171 selection_note_all = ungettext('%(total_count)s selected', 1172 'All %(total_count)s selected', cl.result_count) 1173 context['selection_note_all'] = selection_note_all % {'total_count': cl.result_count} 1174 1173 1175 context.update(extra_context or {}) 1174 1176 context_instance = template.RequestContext(request, current_app=self.admin_site.name) 1175 1177 return render_to_response(self.change_list_template or [ -
./django/contrib/admin/templates/admin/pagination.html
diff --git ./django/contrib/admin/templates/admin/pagination.html ./django/contrib/admin/templates/admin/pagination.html index 3588132..4f28356 100644
6 6 {% paginator_number cl i %} 7 7 {% endfor %} 8 8 {% endif %} 9 {% if cl.result_count != None %} 9 10 {{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %} 11 {% endif %} 10 12 {% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %} 11 13 {% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% trans 'Save' %}"/>{% endif %} 12 14 </p> -
./django/contrib/admin/templatetags/admin_list.py
diff --git ./django/contrib/admin/templatetags/admin_list.py ./django/contrib/admin/templatetags/admin_list.py index fdf082b..96a3c64 100644
from django.template import Library 18 18 register = Library() 19 19 20 20 DOT = '.' 21 NEXT = 'N' 21 22 22 23 def paginator_number(cl,i): 23 24 """ … … def paginator_number(cl,i): 25 26 """ 26 27 if i == DOT: 27 28 return u'... ' 29 elif i == NEXT: 30 return mark_safe(u'<a href="%s"> Next</a> ' % (escape(cl.get_query_string({PAGE_VAR: cl.page_num+1})))) 28 31 elif i == cl.page_num: 29 32 return mark_safe(u'<span class="this-page">%d</span> ' % (i+1)) 30 33 else: 31 return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) 34 if hasattr(cl.paginator, 'num_pages'): 35 return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) 36 else: 37 return mark_safe(u'<a href="%s">%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), i+1)) 38 32 39 paginator_number = register.simple_tag(paginator_number) 33 40 34 41 def pagination(cl): 35 42 """ 36 43 Generates the series of links to the pages in a paginated list. 44 45 If the paginator doesn't support count() then provide next links 37 46 """ 47 # page_num is 0 based here 38 48 paginator, page_num = cl.paginator, cl.page_num 39 49 40 50 pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page … … def pagination(cl): 43 53 else: 44 54 ON_EACH_SIDE = 3 45 55 ON_ENDS = 2 46 47 # If there are 10 or fewer pages, display links to every page. 48 # Otherwise, do some fancy 49 if paginator.num_pages <= 10: 50 page_range = range(paginator.num_pages) 56 if hasattr(paginator, 'num_pages'): 57 # If there are 10 or fewer pages, display links to every page. 58 # Otherwise, do some fancy 59 if paginator.num_pages <= 10: 60 page_range = range(paginator.num_pages) 61 else: 62 # Insert "smart" pagination links, so that there are always ON_ENDS 63 # links at either end of the list of pages, and there are always 64 # ON_EACH_SIDE links at either end of the "current page" link. 65 page_range = [] 66 if page_num > (ON_EACH_SIDE + ON_ENDS): 67 page_range.extend(range(0, ON_EACH_SIDE - 1)) 68 page_range.append(DOT) 69 page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) 70 else: 71 page_range.extend(range(0, page_num + 1)) 72 if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1): 73 page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) 74 page_range.append(DOT) 75 page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages)) 76 else: 77 page_range.extend(range(page_num + 1, paginator.num_pages)) 51 78 else: 52 # Insert "smart" pagination links, so that there are always ON_ENDS 53 # links at either end of the list of pages, and there are always 54 # ON_EACH_SIDE links at either end of the "current page" link. 79 # This paginator doesn't support counts, so provide next link 55 80 page_range = [] 56 81 if page_num > (ON_EACH_SIDE + ON_ENDS): 57 page_range.extend(range(0, ON_EACH_SIDE - 1)) 58 page_range.append(DOT) 59 page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) 60 else: 61 page_range.extend(range(0, page_num + 1)) 62 if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1): 63 page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) 82 page_range.extend(range(0, ON_ENDS)) 64 83 page_range.append(DOT) 65 page_range.extend(range(pag inator.num_pages - ON_ENDS, paginator.num_pages))84 page_range.extend(range(page_num - ON_EACH_SIDE, page_num+1)) 66 85 else: 67 page_range.extend(range(page_num + 1, paginator.num_pages)) 86 page_range.extend(range(0, page_num+1)) 87 # Check if we can get the next page, and put the next link in if so 88 page = paginator.page(page_num+1) 89 if page.has_next(): 90 page_range.extend(NEXT) 68 91 69 92 need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page 70 93 return { … … def search_form(cl): 309 332 """ 310 333 Displays a search form for searching the list. 311 334 """ 312 return { 313 'cl': cl, 314 'show_result_count': cl.result_count != cl.full_result_count, 315 'search_var': SEARCH_VAR 316 } 335 if hasattr(cl, 'result_count'): 336 return { 337 'cl': cl, 338 'show_result_count': cl.result_count != cl.full_result_count, 339 'search_var': SEARCH_VAR 340 } 341 else: 342 return { 343 'cl': cl, 344 'search_var': SEARCH_VAR 345 } 346 317 347 search_form = register.inclusion_tag('admin/search_form.html')(search_form) 318 348 319 349 def admin_list_filter(cl, spec): -
./django/contrib/admin/views/main.py
diff --git ./django/contrib/admin/views/main.py ./django/contrib/admin/views/main.py index 170d168..ff30f7a 100644
class ChangeList(object): 98 98 def get_results(self, request): 99 99 paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page) 100 100 # Get the number of objects, with admin filters applied. 101 result_count = paginator.count 101 if hasattr(paginator, 'count'): 102 result_count = paginator.count 102 103 103 # Get the total number of objects, with no admin filters applied.104 # Perform a slight optimization: Check to see whether any filters were105 # given. If not, use paginator.hits to calculate the number of objects,106 # because we've already done paginator.hits and the value is cached.107 if not self.query_set.query.where:108 full_result_count = result_count109 else:110 full_result_count = self.root_query_set.count()104 # Get the total number of objects, with no admin filters applied. 105 # Perform a slight optimization: Check to see whether any filters were 106 # given. If not, use paginator.hits to calculate the number of objects, 107 # because we've already done paginator.hits and the value is cached. 108 if not self.query_set.query.where: 109 full_result_count = result_count 110 else: 111 full_result_count = self.root_query_set.count() 111 112 112 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED 113 multi_page = result_count > self.list_per_page 113 can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED 114 multi_page = result_count > self.list_per_page 115 self.result_count = result_count 116 self.full_result_count = full_result_count 117 else: 118 can_show_all = False 119 multi_page = True 120 self.full_result_count = True 114 121 115 122 # Get the list of objects to display on this page. 116 123 if (self.show_all and can_show_all) or not multi_page: … … class ChangeList(object): 120 127 result_list = paginator.page(self.page_num+1).object_list 121 128 except InvalidPage: 122 129 raise IncorrectLookupParameters 123 124 self.result_count = result_count 125 self.full_result_count = full_result_count 130 126 131 self.result_list = result_list 127 132 self.can_show_all = can_show_all 128 133 self.multi_page = multi_page -
./django/core/paginator.py
diff --git ./django/core/paginator.py ./django/core/paginator.py index 495cdf2..4232686 100644
class PageNotAnInteger(InvalidPage): 9 9 class EmptyPage(InvalidPage): 10 10 pass 11 11 12 12 13 class Paginator(object): 13 14 def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True): 14 15 self.object_list = object_list … … class Paginator(object): 72 73 """ 73 74 return range(1, self.num_pages + 1) 74 75 page_range = property(_get_page_range) 75 76 76 77 QuerySetPaginator = Paginator # For backwards-compatibility. 77 78 78 79 class Page(object): 79 80 def __init__(self, object_list, number, paginator): 80 81 self.object_list = object_list 81 self.number = number 82 self.number = number # 1-based 82 83 self.paginator = paginator 83 84 84 85 def __repr__(self): … … class Page(object): 118 119 if self.number == self.paginator.num_pages: 119 120 return self.paginator.count 120 121 return self.number * self.paginator.per_page 122 123 124 class UncountedPaginator(object): 125 """Pagination for collections that don't support count()/len()""" 126 127 def __init__(self, object_list, per_page): 128 self.object_list = object_list 129 self.per_page = per_page 130 131 def validate_number(self, number): 132 "Validates the given 1-based page number." 133 try: 134 number = int(number) 135 except ValueError: 136 raise PageNotAnInteger('That page number is not an integer') 137 if number < 1: 138 raise EmptyPage('That page number is less than 1') 139 return number 140 141 def page(self, number): 142 "Returns a UncountedPage object for the given 1-based page number." 143 number = self.validate_number(number) 144 bottom = (number - 1) * self.per_page 145 top = bottom + self.per_page 146 return UncountedPage(self.object_list[bottom:top], number, self) 147 148 149 class UncountedPage(Page): 150 """ Page for collections that don't support count()""" 151 152 def __repr__(self): 153 return '<Page %s>' % (self.number) 154 155 def has_next(self): 156 try: 157 next = self.paginator.object_list[ 158 self.number*self.paginator.per_page] 159 except IndexError: 160 return False 161 162 return True 163 164 def start_index(self): 165 """ 166 Returns the 1-based index of the first object on this page, 167 relative to total objects in the paginator. 168 """ 169 # Special case, return zero if no items. 170 return (self.paginator.per_page * (self.number - 1)) + 1 171 172 def end_index(self): 173 """ 174 Returns the 1-based index of the last object on this page, 175 relative to total objects found (hits). 176 """ 177 return self.number * self.paginator.per_page