Pagination/Filtering Alphabetically

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
import string
from django.core.paginator import InvalidPage, EmptyPage

class NamePaginator(object):
    """Pagination for string-based objects"""
    
    def __init__(self, object_list, on=None, per_page=25):
        self.object_list = object_list
        self.count = len(object_list)
        self.pages = []
        
        # chunk up the objects so we don't need to iterate over the whole list for each letter
        chunks = {}
        
        for obj in self.object_list:
            if on: obj_str = str(getattr(obj, on))
            else: obj_str = str(obj)
            
            letter = str.upper(obj_str[0])
            
            if letter not in chunks: chunks[letter] = []
            
            chunks[letter].append(obj)
        
        # the process for assigning objects to each page
        current_page = NamePage(self)
        
        for letter in string.ascii_uppercase:
            if letter not in chunks: 
                current_page.add([], letter)
                continue
            
            sub_list = chunks[letter] # the items in object_list starting with this letter
            
            new_page_count = len(sub_list) + current_page.count
            # first, check to see if sub_list will fit or it needs to go onto a new page.
            # if assigning this list will cause the page to overflow...
            # and an underflow is closer to per_page than an overflow...
            # and the page isn't empty (which means len(sub_list) > per_page)...
            if new_page_count > per_page and \
                    abs(per_page - current_page.count) < abs(per_page - new_page_count) and \
                    current_page.count > 0:
                # make a new page
                self.pages.append(current_page)
                current_page = NamePage(self)
            
            current_page.add(sub_list, letter)
        
        # if we finished the for loop with a page that isn't empty, add it
        if current_page.count > 0: self.pages.append(current_page)
        
    def page(self, num):
        """Returns a Page object for the given 1-based page number."""
        if len(self.pages) == 0:
            return None
        elif num > 0 and num <= len(self.pages):
            return self.pages[num-1]
        else:
            raise InvalidPage
    
    @property
    def num_pages(self):
        """Returns the total number of pages"""
        return len(self.pages)

class NamePage(object):
    def __init__(self, paginator):
        self.paginator = paginator
        self.object_list = []
        self.letters = []
    
    @property
    def count(self):
        return len(self.object_list)
    
    @property
    def start_letter(self):
        if len(self.letters) > 0: 
            self.letters.sort(key=str.upper)
            return self.letters[0]
        else: return None
    
    @property
    def end_letter(self):
        if len(self.letters) > 0: 
            self.letters.sort(key=str.upper)
            return self.letters[-1]
        else: return None
    
    @property
    def number(self):
        return self.paginator.pages.index(self) + 1
    
    def add(self, new_list, letter=None):
        if len(new_list) > 0: self.object_list = self.object_list + new_list
        if letter: self.letters.append(letter)
    
    def __repr__(self):
        if self.start_letter == self.end_letter:
            return self.start_letter
        else:
            return '%c-%c' % (self.start_letter, self.end_letter)

More like this

  1. Page numbers with ... like in Digg by Ciantic 5 years ago
  2. Digg-like pagination by SmileyChris 4 years, 10 months ago
  3. Paginator TemplateTag by trbs 6 years ago
  4. Simple Paginator Function by goodsanket 4 years, 2 months ago
  5. Improved many-page pagination by dokterbob 3 years, 7 months ago

Comments

HM (on March 21, 2009):

Very useful. I've made it unicode-safe (replace all str() with unicode() and the __repr__() with __unicode__(), iterate on chunks sorted by key instead of iterating on string.ascii_uppercase, don't use str().upper() but <obj>.upper() instead) and more complete. Working on putting objects starting with numbers or symbols (in the unicode sense) in their own chunk too.

#

rm (on July 14, 2009):

Thanks for the snippet and thanks to HM for the unicode-ification. Said that, there are a couple of typos in the template: need ifequal instead if and p instead of page. Should be:

{% ifequal p page %}

<span class="selected">{{ page }}</span>

{% else %}

<a href="?page={{ p.number }}">{{ p }}</a>

{% endifequal %}

#

datakid23 (on April 28, 2010):

How hard would it be to change the pagination a little to offer 26 pages, 1 per letter, instead of x pages, 25 (or y) entries per page (across letters)? Is this a good place to start, or should I be using another alg?

#

slafs (on May 18, 2010):

@datakid23

what You talking about is not a pagination but a filtration

#

slafs (on May 18, 2010):

You can look at #2025 ;]

#

mhulse (on June 29, 2010):

HM, just curious if you ever finished working on your version of the code? If so, would you ever consider posting it for others to learn from?

#

vemubalu (on August 30, 2010):

Hi

It would be good to have a configurable parameter which if specified would accordingly paginate alphabetically or in groups

For example I want the whole letters to be displayed (Even return blank page if there are no objects for the alphabet searched )

#

andreslucena (on November 21, 2010):

@HM it is possible to you to update the unicode stuff?? It would be nice ;)

#

(Forgotten your password?)