Ticket #8408: paginator-patch.diff

File paginator-patch.diff, 13.2 KB (added by Malcolm Box, 13 years ago)

Patch admin classes to add UncountedPaginator

  • ./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):  
    11511151        else:
    11521152            action_form = None
    11531153
    1154         selection_note_all = ungettext('%(total_count)s selected',
    1155             'All %(total_count)s selected', cl.result_count)
    1156 
    11571154        context = {
    11581155            'module_name': force_unicode(opts.verbose_name_plural),
    11591156            'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
    1160             'selection_note_all': selection_note_all % {'total_count': cl.result_count},
    11611157            'title': cl.title,
    11621158            'is_popup': cl.is_popup,
    11631159            'cl': cl,
    class ModelAdmin(BaseModelAdmin):  
    11701166            'actions_on_bottom': self.actions_on_bottom,
    11711167            'actions_selection_counter': self.actions_selection_counter,
    11721168        }
     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
    11731175        context.update(extra_context or {})
    11741176        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
    11751177        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
     
    66    {% paginator_number cl i %}
    77{% endfor %}
    88{% endif %}
     9{% if cl.result_count != None %}
    910{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
     11{% endif %}
    1012{% if show_all_url %}&nbsp;&nbsp;<a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
    1113{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% trans 'Save' %}"/>{% endif %}
    1214</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  
    1818register = Library()
    1919
    2020DOT = '.'
     21NEXT = 'N'
    2122
    2223def paginator_number(cl,i):
    2324    """
    def paginator_number(cl,i):  
    2526    """
    2627    if i == DOT:
    2728        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}))))
    2831    elif i == cl.page_num:
    2932        return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
    3033    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           
    3239paginator_number = register.simple_tag(paginator_number)
    3340
    3441def pagination(cl):
    3542    """
    3643    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
    3746    """
     47    # page_num is 0 based here
    3848    paginator, page_num = cl.paginator, cl.page_num
    3949
    4050    pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
    def pagination(cl):  
    4353    else:
    4454        ON_EACH_SIDE = 3
    4555        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))
    5178        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
    5580            page_range = []
    5681            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))
    6483                page_range.append(DOT)
    65                 page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages))
     84                page_range.extend(range(page_num - ON_EACH_SIDE, page_num+1))
    6685            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)
    6891
    6992    need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
    7093    return {
    def search_form(cl):  
    309332    """
    310333    Displays a search form for searching the list.
    311334    """
    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       
    317347search_form = register.inclusion_tag('admin/search_form.html')(search_form)
    318348
    319349def 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):  
    9898    def get_results(self, request):
    9999        paginator = self.model_admin.get_paginator(request, self.query_set, self.list_per_page)
    100100        # Get the number of objects, with admin filters applied.
    101         result_count = paginator.count
     101        if hasattr(paginator, 'count'):
     102            result_count = paginator.count
    102103
    103         # Get the total number of objects, with no admin filters applied.
    104         # Perform a slight optimization: Check to see whether any filters were
    105         # 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_count
    109         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()
    111112
    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
    114121
    115122        # Get the list of objects to display on this page.
    116123        if (self.show_all and can_show_all) or not multi_page:
    class ChangeList(object):  
    120127                result_list = paginator.page(self.page_num+1).object_list
    121128            except InvalidPage:
    122129                raise IncorrectLookupParameters
    123 
    124         self.result_count = result_count
    125         self.full_result_count = full_result_count
     130           
    126131        self.result_list = result_list
    127132        self.can_show_all = can_show_all
    128133        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):  
    99class EmptyPage(InvalidPage):
    1010    pass
    1111
     12   
    1213class Paginator(object):
    1314    def __init__(self, object_list, per_page, orphans=0, allow_empty_first_page=True):
    1415        self.object_list = object_list
    class Paginator(object):  
    7273        """
    7374        return range(1, self.num_pages + 1)
    7475    page_range = property(_get_page_range)
    75 
     76 
    7677QuerySetPaginator = Paginator # For backwards-compatibility.
    7778
    7879class Page(object):
    7980    def __init__(self, object_list, number, paginator):
    8081        self.object_list = object_list
    81         self.number = number
     82        self.number = number  # 1-based
    8283        self.paginator = paginator
    8384
    8485    def __repr__(self):
    class Page(object):  
    118119        if self.number == self.paginator.num_pages:
    119120            return self.paginator.count
    120121        return self.number * self.paginator.per_page
     122
     123
     124class 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
     149class 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
Back to Top