Pagination Alphabetically compatible with paginator_class

  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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import string
from django.core.paginator import InvalidPage, EmptyPage

class NamePaginator(object):
    """Pagination for string-based objects"""

    def __init__(self, queryset, paginate_by=25, orphans=0, allow_empty_first_page=True):
        # We ignore allow_empty_first_page and orphans, just here for compliance
        self.pages = []
        self.object_list = queryset
        self.count = len(self.object_list)

        # chunk up the objects so we don't need to iterate over the whole list for each letter
        chunks = {}

        # we sort them by the first model ordering key
        for obj in self.object_list:
            if queryset: obj_str = unicode(getattr(obj, obj._meta.ordering[0]))
            else: obj_str = unicode(obj)

            # some of my models had "first_name" "last_name" sorting, and some first_names
            # were empty so if it fails you can try sorting by the second ordering key
            # which worked for me but do your own thing
            try:
                letter = unicode.upper(obj_str[0])
            except:
                obj_str = unicode(getattr(obj, obj._meta.ordering[1]))
                letter = unicode.upper(obj_str[1])

            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 > paginate_by and \
                    abs(paginate_by - current_page.count) < abs(paginate_by - 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

    # just added the methods I needed to use in the templates
    # feel free to add the ones you need too
    def has_other_pages(self):
        return len(self.object_list) > 0

    def has_previous(self):
        return self.paginator.pages.index(self)

    def has_next(self):
        return self.paginator.pages.index(self) + 2

    def next_page_number(self):
        return self.paginator.pages.index(self) + 2

    def previous_page_number(self):
        return self.paginator.pages.index(self)

    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. Digg-like pagination by SmileyChris 4 years, 11 months ago
  2. better paginator template tag by amitu 5 years, 6 months ago
  3. Paginator TemplateTag by trbs 6 years ago
  4. load m2m fields objects by dirol 3 years, 10 months ago
  5. Page numbers with ... like in Digg by Ciantic 5 years ago

Comments

stevenjoseph (on April 19, 2012):

Thanks for this, made a few modifications which might be useful:

16c16,17
<             if on: obj_str = str(getattr(obj, on))
---
>             if on !=None:
>                 obj_str = str(getattr(obj, on)) if isinstance(obj,(str,unicode)) else obj[on]
18,20c19,21       
<             letter = str.upper(obj_str[0])   
--- 
>             letter = obj_str[0].upper()

#

(Forgotten your password?)